/*
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
 distributed with this work for additional information
 regarding copyright ownership.  The ASF licenses this file
 to you under the Apache License, Version 2.0 (the
 "License"); you may not use this file except in compliance
 with the License.  You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
*/

package resources

import (
	"fmt"
	"math"
	"reflect"
	"testing"

	"golang.org/x/exp/maps"
	"gotest.tools/v3/assert"

	"github.com/apache/yunikorn-scheduler-interface/lib/go/common"
)

func CheckLenOfResource(res *Resource, expected int) (bool, string) {
	if got := len(res.Resources); expected == 0 && (res == nil || got != expected) {
		return false, fmt.Sprintf("input with empty and nil should be a empty resource: Expected %d, got %d", expected, got)
	}
	if got := len(res.Resources); got != expected {
		return false, fmt.Sprintf("Length of resources is wrong: Expected %d, got %d", expected, got)
	}
	return true, ""
}

func ChecResourceValueOfResource(res *Resource, expected map[string]Quantity) (bool, string) {
	for key, expected := range expected {
		if got := res.Resources[key]; got != expected {
			return false, fmt.Sprintf("resource %s, expected %d, got %d", key, expected, got)
		}
	}
	return true, ""
}

func TestNewResourceFromMap(t *testing.T) {
	type outputs struct {
		length    int
		resources map[string]Quantity
	}
	var tests = []struct {
		caseName string
		input    map[string]Quantity
		expected outputs
	}{
		{
			"nil",
			nil,
			outputs{0, map[string]Quantity{}},
		},
		{
			"empty",
			map[string]Quantity{},
			outputs{0, map[string]Quantity{}},
		},
		{
			"one resource",
			map[string]Quantity{"first": 1},
			outputs{1, map[string]Quantity{"first": 1}},
		},
		{
			"two resource",
			map[string]Quantity{"first": 0, "second": -1},
			outputs{2, map[string]Quantity{"first": 0, "second": -1}},
		},
		{
			"three resource",
			map[string]Quantity{"first": 1, "second": 2, "third": 0},
			outputs{3, map[string]Quantity{"first": 1, "second": 2, "third": 0}},
		},
	}
	for _, tt := range tests {
		t.Run(tt.caseName, func(t *testing.T) {
			res := NewResourceFromMap(tt.input)
			if ok, err := CheckLenOfResource(res, tt.expected.length); !ok {
				t.Error(err)
			} else {
				if ok, err := ChecResourceValueOfResource(res, tt.expected.resources); !ok {
					t.Error(err)
				}
			}
		})
	}
}

func TestNewResourceFromConf(t *testing.T) {
	type expectedvalues struct {
		resourceExist bool
		resourceTypes int
		resources     string
	}
	var tests = []struct {
		caseName string
		input    map[string]string
		expected expectedvalues
	}{
		{"resource with nil input", nil, expectedvalues{true, 0, ""}},
		{"resource without any resource mappings", make(map[string]string), expectedvalues{true, 0, ""}},
		{"resource with zero value", map[string]string{"zero": "0"}, expectedvalues{true, 1, "map[zero:0]"}},
		{"normal multipliers with \"M\"", map[string]string{"memory": "10M"}, expectedvalues{true, 1, "map[memory:10000000]"}},
		{"normal multipliers with \"Mi\"", map[string]string{"memory": "10Mi"}, expectedvalues{true, 1, "map[memory:10485760]"}},
		{"vcore multipliers with \"k\"", map[string]string{"vcore": "10k"}, expectedvalues{true, 1, "map[vcore:10000000]"}},
		{"vcore multipliers", map[string]string{"vcore": "10"}, expectedvalues{true, 1, "map[vcore:10000]"}},
		{"vcore multipliers with \"m\"", map[string]string{"vcore": "10m"}, expectedvalues{true, 1, "map[vcore:10]"}},
		{"failure case: parse error", map[string]string{"fail": "xx"}, expectedvalues{false, 0, ""}},
		{"negative resource", map[string]string{"memory": "-15"}, expectedvalues{false, 0, ""}},
		{"nagative resource for vcore", map[string]string{"vcore": "-15"}, expectedvalues{false, 0, ""}},
		{"\"milli\" used for anything other than vcore", map[string]string{"memory": "10m"}, expectedvalues{false, 0, ""}},
	}
	for _, tt := range tests {
		t.Run(tt.caseName, func(t *testing.T) {
			original, err := NewResourceFromConf(tt.input)
			if tt.expected.resourceExist {
				if err != nil || len(original.Resources) != tt.expected.resourceTypes {
					t.Errorf("Expected err nil, numbers of resource types %v. Got err %v, numbers of resource types %v", tt.expected.resourceTypes, err, len(original.Resources))
				}
				if len(tt.expected.resources) > 0 && tt.expected.resources != original.String() {
					t.Errorf("Resource value:got %s, expected %s", original.String(), tt.expected.resources)
				}
			}

			if !tt.expected.resourceExist {
				if err == nil || original != nil {
					t.Errorf("new resource is created and the function should have returned error. got err %v, res %v", err, original)
				}
			}
		})
	}
}

func TestCloneNil(t *testing.T) {
	// make sure we're nil safe IDE will complain about the non nil check
	defer func() {
		if r := recover(); r != nil {
			t.Fatal("panic on nil resource in clone test")
		}
	}()
	var empty *Resource
	if empty.Clone() != nil {
		t.Fatalf("clone of nil resource should be nil")
	}
}

func TestClone(t *testing.T) {
	var tests = []struct {
		caseName string
		input    map[string]Quantity
	}{
		{"resource without any resource mappings", map[string]Quantity{}},
		{"resource mappings", map[string]Quantity{"first": 1, "second": -2, "third": 3}},
		{"set a resource value to 0", map[string]Quantity{"first": 1, "zero": 0, "third": 3}},
	}
	for _, tt := range tests {
		t.Run(tt.caseName, func(t *testing.T) {
			original := NewResourceFromMap(tt.input)
			cloned := original.Clone()
			if original == cloned {
				t.Errorf("cloned resources pointers are equal, should not be")
			}
			if !reflect.DeepEqual(original.Resources, cloned.Resources) {
				t.Errorf("cloned resources are not equal: %v / %v", original, cloned)
			}
		})
	}
}

func TestEquals(t *testing.T) {
	type inputs struct {
		base    map[string]Quantity
		compare map[string]Quantity
	}
	var tests = []struct {
		caseName string
		input    inputs
		expected bool
	}{
		{"simple cases (nil checks)", inputs{nil, nil}, true},
		{"simple cases (nil checks)", inputs{map[string]Quantity{}, nil}, false},
		{"simple cases (nil checks)", inputs{map[string]Quantity{}, map[string]Quantity{"zero": 0}}, true},
		{"Empty mapping and resource containing zero value are same", inputs{map[string]Quantity{}, map[string]Quantity{"zero": 0}}, true},
		{"Same key and different resource value", inputs{map[string]Quantity{"first": 10}, map[string]Quantity{"first": 0}}, false},
		{"Different key and same resource value", inputs{map[string]Quantity{"first": 10}, map[string]Quantity{"second": 10}}, false},
		{"Same key and same resource value", inputs{map[string]Quantity{"first": 10}, map[string]Quantity{"first": 10}}, true},
		{"Different types of keys", inputs{map[string]Quantity{"first": 1}, map[string]Quantity{"first": 1, "second": 1}}, false},
	}
	for _, tt := range tests {
		t.Run(tt.caseName, func(t *testing.T) {
			var base, compare *Resource
			if tt.input.base != nil {
				base = NewResourceFromMap(tt.input.base)
			}
			if tt.input.compare != nil {
				compare = NewResourceFromMap(tt.input.compare)
			}

			if result := Equals(base, compare); result != tt.expected {
				t.Errorf("Equal result should be %v instead of %v, left %v, right %v", tt.expected, result, base, compare)
			}
			if result := Equals(compare, base); result != tt.expected {
				t.Errorf("Equal result should be %v instead of %v, left %v, right %v", tt.expected, result, compare, base)
			}
		})
	}
}

func TestIsZero(t *testing.T) {
	var tests = []struct {
		caseName string
		input    map[string]Quantity
		expected bool
	}{
		{"nil checks", nil, true},
		{"Empty resource value", map[string]Quantity{}, true},
		{"Zero resource value", map[string]Quantity{"zero": 0}, true},
		{"Resource value", map[string]Quantity{"first": 10}, false},
		{"Multiple resource values including zero", map[string]Quantity{"first": 10, "second": 2, "third": 0}, false},
		{"Multiple resource values are zero", map[string]Quantity{"first": 0, "second": 0, "third": 0}, true},
		{"Multiple resource values", map[string]Quantity{"first": 10, "second": 2, "third": 4}, false},
	}
	for _, tt := range tests {
		t.Run(tt.caseName, func(t *testing.T) {
			var base *Resource
			if base = nil; tt.input != nil {
				base = NewResourceFromMap(tt.input)
			}
			if result := IsZero(base); result != tt.expected {
				t.Errorf("Is base %v zero?, got %v, expected %v", base, result, tt.expected)
			}
		})
	}
}

func TestStrictlyGreaterThanZero(t *testing.T) {
	var tests = []struct {
		caseName string
		input    map[string]Quantity
		expected bool
	}{
		{"nil resource should be smaller than zero", nil, false},
		{"no resource entries should be smaller than zero", map[string]Quantity{}, false},
		{"only zero resources should be smaller than zero", map[string]Quantity{"zero": 0}, false},
		{"only positive resource should be greater than zero", map[string]Quantity{"first": 10}, true},
		{"negative resource should be smaller than zero", map[string]Quantity{"first": -1}, false},
		{"multiple resources containing negative resources should be smaller than zero", map[string]Quantity{"first": -1, "second": 1, "third": -1}, false},
	}
	for _, tt := range tests {
		t.Run(tt.caseName, func(t *testing.T) {
			var base *Resource
			if tt.input != nil {
				base = NewResourceFromMap(tt.input)
			}
			if result := StrictlyGreaterThanZero(base); result != tt.expected {
				t.Errorf("StrictlyGreaterThanZero: got %v, expected %v", result, tt.expected)
			}
		})
	}
}

func TestStrictlyGreaterThan(t *testing.T) {
	type inputs struct {
		larger  map[string]Quantity
		smaller map[string]Quantity
		sameRef bool
	}
	type outputs struct {
		larger  bool
		smaller bool
	}
	var tests = []struct {
		caseName string
		input    inputs
		expected outputs
	}{
		{"Nil check", inputs{nil, nil, true}, outputs{false, false}},
		{"Empty resources", inputs{map[string]Quantity{}, map[string]Quantity{}, false}, outputs{false, false}},
		{"An empty resource and a zero resource", inputs{map[string]Quantity{"zero": 0}, map[string]Quantity{}, false}, outputs{false, false}},
		{"Zero resources", inputs{map[string]Quantity{"zero": 0}, map[string]Quantity{"zero": 0}, false}, outputs{false, false}},
		{"An empty resource and a negative resource", inputs{map[string]Quantity{}, map[string]Quantity{"first": -1}, false}, outputs{true, false}},
		{"Positive resource and same references", inputs{map[string]Quantity{"first": 10}, nil, true}, outputs{false, false}},
		{"Positive numbers", inputs{map[string]Quantity{"first": 10}, map[string]Quantity{"first": 1}, false}, outputs{true, false}},
		{"Different resources", inputs{map[string]Quantity{"first": 10}, map[string]Quantity{"second": 1}, false}, outputs{false, false}},
		{"Mutiple resources and a single resource", inputs{map[string]Quantity{"first": 10, "second": 10}, map[string]Quantity{"first": 1}, false}, outputs{true, false}},
		{"Negative resource is smaller than not set", inputs{map[string]Quantity{"first": 10, "second": -1}, map[string]Quantity{"first": 10}, false}, outputs{false, true}},
		{"Negative resource is smaller than not set", inputs{map[string]Quantity{"first": -1}, map[string]Quantity{}, false}, outputs{false, true}},
	}
	for _, tt := range tests {
		t.Run(tt.caseName, func(t *testing.T) {
			var compare, base *Resource
			if tt.input.larger != nil {
				compare = NewResourceFromMap(tt.input.larger)
			}
			if tt.input.sameRef {
				base = compare
			} else {
				base = NewResourceFromMap(tt.input.smaller)
			}
			if result := StrictlyGreaterThan(compare, base); result != tt.expected.larger {
				t.Errorf("comapre %v, base %v, got %v, expeceted %v", compare, base, result, tt.expected.larger)
			}
			if result := StrictlyGreaterThan(base, compare); result != tt.expected.smaller {
				t.Errorf("base %v, compare %v, got %v, expeceted %v", base, compare, result, tt.expected.smaller)
			}
		})
	}
}

func TestMatchAnyOnlyExisting(t *testing.T) {
	var tests = []struct {
		caseName string
		left     map[string]Quantity
		right    map[string]Quantity
		expected bool
	}{
		{"nil resource should not match", nil, nil, false},
		{"empty resource should not match", map[string]Quantity{}, map[string]Quantity{}, false},
		{"equal positive resources should match", map[string]Quantity{"first": 1}, map[string]Quantity{"first": 1}, true},
		{"equal positive resources with different values should match", map[string]Quantity{"first": 1}, map[string]Quantity{"first": 2}, true},
		{"positive resource and nil resource should not match", map[string]Quantity{"first": 1}, nil, false},
		{"nil resource and positive resource should not match", nil, map[string]Quantity{"first": 1}, false},
		{"positive resource and empty resource should not match", map[string]Quantity{"first": 1}, map[string]Quantity{}, false},
		{"empty resource and positive resource should not match", map[string]Quantity{}, map[string]Quantity{"first": 1}, false},
		{"positive resource should match even though value is zero", map[string]Quantity{"zero": 0}, map[string]Quantity{"zero": 0}, true},
		{"positive resource should match even though extra resource type is there", map[string]Quantity{"first": 10}, map[string]Quantity{"first": 10, "second": 1}, true},
		{"positive resource should match even though extra resource type is there", map[string]Quantity{"first": 10, "second": 1}, map[string]Quantity{"first": 10}, true},
		{"resource should not match", map[string]Quantity{"first": 10}, map[string]Quantity{"second": 10}, false},
		{"resource should not match", map[string]Quantity{"second": 10}, map[string]Quantity{"first": 10}, false},
	}
	for _, tt := range tests {
		t.Run(tt.caseName, func(t *testing.T) {
			var left *Resource
			var right *Resource
			if tt.left != nil {
				left = NewResourceFromMap(tt.left)
			}
			if tt.right != nil {
				right = NewResourceFromMap(tt.right)
			}
			if result := left.MatchAny(right); result != tt.expected {
				t.Errorf("MatchAny: got %v, expected %v", result, tt.expected)
			}
		})
	}

	// case: left and right resource is same instance
	quantity := map[string]Quantity{"first": 1}
	left := NewResourceFromMap(quantity)
	right := left
	result := left.MatchAny(right)
	assert.Assert(t, result)
}

func TestStrictlyGreaterThanOnlyExisting(t *testing.T) {
	type inputs struct {
		larger  map[string]Quantity
		smaller map[string]Quantity
		sameRef bool
	}
	type outputs struct {
		larger  bool
		smaller bool
	}
	var tests = []struct {
		caseName string
		input    inputs
		expected outputs
	}{
		{"Nil check", inputs{nil, nil, false}, outputs{false, false}},
		{"Positive resource and empty resources", inputs{map[string]Quantity{"first": 10}, map[string]Quantity{}, false}, outputs{true, false}},
		{"Positive resource and nil resources", inputs{map[string]Quantity{"first": 10}, nil, false}, outputs{true, false}},

		{"Equal Positive resources", inputs{map[string]Quantity{"first": 10}, map[string]Quantity{"first": 10}, false}, outputs{false, false}},
		{"Different Positive resources", inputs{map[string]Quantity{"first": 10}, map[string]Quantity{"first": 20}, false}, outputs{false, true}},
		{"Different Positive resources", inputs{map[string]Quantity{"first": 10}, map[string]Quantity{"first": 5}, false}, outputs{true, false}},

		{"Equal Positive resources with extra resource types", inputs{map[string]Quantity{"first": 10}, map[string]Quantity{"first": 10, "sec": 10}, false}, outputs{false, false}},
		{"Different Positive resources with extra resource types", inputs{map[string]Quantity{"first": 10}, map[string]Quantity{"first": 20, "sec": 10}, false}, outputs{false, true}},
		{"Different Positive resources with extra resource types", inputs{map[string]Quantity{"first": 10}, map[string]Quantity{"first": 5, "sec": 10}, false}, outputs{true, false}},

		{"Equal Positive resources but completely disjoint", inputs{map[string]Quantity{"first": 10}, map[string]Quantity{"sec": 10}, false}, outputs{true, true}},
		{"Zero resource and empty resources", inputs{map[string]Quantity{"first": 0}, map[string]Quantity{}, false}, outputs{false, false}},
		{"Zero resource and nil resources", inputs{map[string]Quantity{"first": 0}, nil, false}, outputs{false, false}},

		{"Negative resource and empty resources", inputs{map[string]Quantity{"first": -10}, map[string]Quantity{}, false}, outputs{false, false}},
		{"Negative resource and nil resources", inputs{map[string]Quantity{"first": -10}, nil, false}, outputs{false, false}},
		{"Negative resource and empty resources", inputs{map[string]Quantity{"first": -10, "sec": 10}, map[string]Quantity{}, false}, outputs{false, false}},
		{"Negative resource and nil resources", inputs{map[string]Quantity{"first": -10, "sec": 10}, nil, false}, outputs{false, false}},

		{"Equal Negative resources", inputs{map[string]Quantity{"first": -10}, map[string]Quantity{"first": -10}, false}, outputs{false, false}},
		{"Different Negative resources", inputs{map[string]Quantity{"first": -10}, map[string]Quantity{"first": -20}, false}, outputs{true, false}},
		{"Different Negative resources", inputs{map[string]Quantity{"first": -10}, map[string]Quantity{"first": -5}, false}, outputs{false, true}},

		{"Equal Negative resources with extra resource types", inputs{map[string]Quantity{"first": -10}, map[string]Quantity{"first": -10, "sec": 10}, false}, outputs{false, false}},
		{"Different Negative resources with extra resource types", inputs{map[string]Quantity{"first": -10}, map[string]Quantity{"first": -20, "sec": 10}, false}, outputs{true, false}},
		{"Different Negative resources with extra resource types", inputs{map[string]Quantity{"first": -10}, map[string]Quantity{"first": -5, "sec": 10}, false}, outputs{false, true}},

		{"Equal Negative resources but completely disjoint", inputs{map[string]Quantity{"first": -10}, map[string]Quantity{"sec": -10}, false}, outputs{true, true}},
	}
	for _, tt := range tests {
		t.Run(tt.caseName, func(t *testing.T) {
			var compare, base *Resource
			if tt.input.larger != nil {
				compare = NewResourceFromMap(tt.input.larger)
			}
			if tt.input.sameRef {
				base = compare
			} else {
				base = NewResourceFromMap(tt.input.smaller)
			}
			if result := compare.StrictlyGreaterThanOnlyExisting(base); result != tt.expected.larger {
				t.Errorf("comapre %v, base %v, got %v, expeceted %v", compare, base, result, tt.expected.larger)
			}
			if result := base.StrictlyGreaterThanOnlyExisting(compare); result != tt.expected.smaller {
				t.Errorf("base %v, compare %v, got %v, expeceted %v", base, compare, result, tt.expected.smaller)
			}
		})
	}
}

func TestStrictlyGreaterThanOrEqualsOnlyExisting(t *testing.T) {
	type inputs struct {
		larger  map[string]Quantity
		smaller map[string]Quantity
		sameRef bool
	}
	type outputs struct {
		larger  bool
		smaller bool
	}
	var tests = []struct {
		caseName string
		input    inputs
		expected outputs
	}{
		{"Nil check", inputs{nil, nil, false}, outputs{false, false}},
		{"Positive resource and empty resources", inputs{map[string]Quantity{"first": 10}, map[string]Quantity{}, false}, outputs{true, false}},
		{"Positive resource and nil resources", inputs{map[string]Quantity{"first": 10}, nil, false}, outputs{true, false}},

		{"Equal Positive resources", inputs{map[string]Quantity{"first": 10}, map[string]Quantity{"first": 10}, false}, outputs{true, true}},
		{"Different Positive resources", inputs{map[string]Quantity{"first": 10}, map[string]Quantity{"first": 20}, false}, outputs{false, true}},
		{"Different Positive resources", inputs{map[string]Quantity{"first": 10}, map[string]Quantity{"first": 5}, false}, outputs{true, false}},

		{"Equal Positive resources with extra resource types", inputs{map[string]Quantity{"first": 10}, map[string]Quantity{"first": 10, "sec": 10}, false}, outputs{true, true}},
		{"Different Positive resources with extra resource types", inputs{map[string]Quantity{"first": 10}, map[string]Quantity{"first": 20, "sec": 10}, false}, outputs{false, true}},
		{"Different Positive resources with extra resource types", inputs{map[string]Quantity{"first": 10}, map[string]Quantity{"first": 5, "sec": 10}, false}, outputs{true, false}},

		{"Equal Positive resources but completely disjoint", inputs{map[string]Quantity{"first": 10}, map[string]Quantity{"sec": 10}, false}, outputs{true, true}},
		{"Zero resource and empty resources", inputs{map[string]Quantity{"first": 0}, map[string]Quantity{}, false}, outputs{false, false}},
		{"Zero resource and nil resources", inputs{map[string]Quantity{"first": 0}, nil, false}, outputs{false, false}},

		{"Negative resource and empty resources", inputs{map[string]Quantity{"first": -10}, map[string]Quantity{}, false}, outputs{false, false}},
		{"Negative resource and nil resources", inputs{map[string]Quantity{"first": -10}, nil, false}, outputs{false, false}},
		{"Negative resource and empty resources", inputs{map[string]Quantity{"first": -10, "sec": 10}, map[string]Quantity{}, false}, outputs{false, false}},
		{"Negative resource and nil resources", inputs{map[string]Quantity{"first": -10, "sec": 10}, nil, false}, outputs{false, false}},

		{"Equal Negative resources", inputs{map[string]Quantity{"first": -10}, map[string]Quantity{"first": -10}, false}, outputs{true, true}},
		{"Different Negative resources", inputs{map[string]Quantity{"first": -10}, map[string]Quantity{"first": -20}, false}, outputs{true, false}},
		{"Different Negative resources", inputs{map[string]Quantity{"first": -10}, map[string]Quantity{"first": -5}, false}, outputs{false, true}},

		{"Equal Negative resources with extra resource types", inputs{map[string]Quantity{"first": -10}, map[string]Quantity{"first": -10, "sec": 10}, false}, outputs{true, true}},
		{"Different Negative resources with extra resource types", inputs{map[string]Quantity{"first": -10}, map[string]Quantity{"first": -20, "sec": 10}, false}, outputs{true, false}},
		{"Different Negative resources with extra resource types", inputs{map[string]Quantity{"first": -10}, map[string]Quantity{"first": -5, "sec": 10}, false}, outputs{false, true}},

		{"Equal Negative resources but completely disjoint", inputs{map[string]Quantity{"first": -10}, map[string]Quantity{"sec": -10}, false}, outputs{true, true}},
	}
	for _, tt := range tests {
		t.Run(tt.caseName, func(t *testing.T) {
			var compare, base *Resource
			if tt.input.larger != nil {
				compare = NewResourceFromMap(tt.input.larger)
			}
			if tt.input.sameRef {
				base = compare
			} else {
				base = NewResourceFromMap(tt.input.smaller)
			}
			if result := compare.StrictlyGreaterThanOrEqualsOnlyExisting(base); result != tt.expected.larger {
				t.Errorf("comapre %v, base %v, got %v, expeceted %v", compare, base, result, tt.expected.larger)
			}
			if result := base.StrictlyGreaterThanOrEqualsOnlyExisting(compare); result != tt.expected.smaller {
				t.Errorf("base %v, compare %v, got %v, expeceted %v", base, compare, result, tt.expected.smaller)
			}
		})
	}
}

func TestStrictlyGreaterThanOrEquals(t *testing.T) {
	type inputs struct {
		larger  map[string]Quantity
		smaller map[string]Quantity
		sameRef bool
	}
	var tests = []struct {
		caseName string
		input    inputs
		expected [2]bool
	}{
		{"Nil check", inputs{nil, nil, true}, [2]bool{true, true}},
		{"Empty resource values", inputs{map[string]Quantity{}, map[string]Quantity{}, false}, [2]bool{true, true}},
		{"Zero resource value and empty resource value", inputs{map[string]Quantity{"zero": 0}, map[string]Quantity{}, false}, [2]bool{true, true}},
		{"Zero resource values", inputs{map[string]Quantity{"zero": 0}, map[string]Quantity{"other": 0}, false}, [2]bool{true, true}},
		{"Positive resource value and empty resource value", inputs{map[string]Quantity{"first": 10}, map[string]Quantity{"other": 0}, false}, [2]bool{true, false}},
		{"Different positive resource values", inputs{map[string]Quantity{"first": 10}, map[string]Quantity{"second": 10}, false}, [2]bool{false, false}},
		{"Negative resource is smaller than not set", inputs{map[string]Quantity{"first": 1, "second": -1}, map[string]Quantity{"first": 1}, false}, [2]bool{false, true}},
	}
	for _, tt := range tests {
		t.Run(tt.caseName, func(t *testing.T) {
			var compare, base *Resource
			if tt.input.larger != nil {
				compare = NewResourceFromMap(tt.input.larger)
			}
			if tt.input.sameRef {
				base = compare
			} else {
				base = NewResourceFromMap(tt.input.smaller)
			}

			result := StrictlyGreaterThanOrEquals(compare, base)
			if result != tt.expected[0] {
				t.Errorf("comapre %v, base %v, got %v, expeceted %v", compare, base, result, tt.expected[0])
			}
			result = StrictlyGreaterThanOrEquals(base, compare)
			if result != tt.expected[1] {
				t.Errorf("base %v, compare %v, got %v, expeceted %v", base, compare, result, tt.expected[1])
			}
		})
	}
}

func TestComponentWiseMin(t *testing.T) {
	smallerRes := NewResourceFromMap(map[string]Quantity{"first": 5, "second": 15, "third": 6})
	higherRes := NewResourceFromMap(map[string]Quantity{"first": 7, "second": 10, "forth": 6})
	expected := NewResourceFromMap(map[string]Quantity{"first": 5, "second": 10, "third": 6, "forth": 6})

	testCases := []struct {
		name     string
		res1     *Resource
		res2     *Resource
		expected *Resource
	}{
		{"Both resources nil", nil, nil, nil},
		{"First resource nil", nil, smallerRes, smallerRes},
		{"Second resource nil", smallerRes, nil, smallerRes},
		{"First resource smaller than the second", smallerRes, higherRes, expected},
		{"Second resource smaller than the first", higherRes, smallerRes, expected},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			result := ComponentWiseMin(tc.res1, tc.res2)
			assert.DeepEqual(t, result, tc.expected)
		})
	}
}

func TestComponentWiseMinOnlyExisting(t *testing.T) {
	testCases := []struct {
		name     string
		left     map[string]Quantity
		right    map[string]Quantity
		expected map[string]Quantity
	}{
		{"Min of nil resources should be nil", nil, nil, nil},
		{"Min of empty resources should be empty resource ", map[string]Quantity{}, map[string]Quantity{}, map[string]Quantity{}},
		{"Min of positive resource and nil resource", map[string]Quantity{"first": 5}, nil, map[string]Quantity{"first": 5}},
		{"Min of nil resource and positive resource", nil, map[string]Quantity{"first": 5}, nil},
		{"Min of two positive resources", map[string]Quantity{"first": 5}, map[string]Quantity{"first": 10}, map[string]Quantity{"first": 5}},
		{"Min of two positive resources", map[string]Quantity{"first": 10}, map[string]Quantity{"first": 5}, map[string]Quantity{"first": 5}},
		{"Min of positive resource and negative resource", map[string]Quantity{"first": 5}, map[string]Quantity{"first": -5}, map[string]Quantity{"first": -5}},
		{"Min of positive resource and negative resource", map[string]Quantity{"first": -5}, map[string]Quantity{"first": 5}, map[string]Quantity{"first": -5}},
		{"Min of two positive resources with extra resource types", map[string]Quantity{"first": 10}, map[string]Quantity{"first": 5, "second": 15}, map[string]Quantity{"first": 5}},
		{"Min of two positive resources with extra resource types", map[string]Quantity{"first": 5, "second": 15}, map[string]Quantity{"first": 10}, map[string]Quantity{"first": 5, "second": 15}},
		{"Min of positive resource and negative resource with extra resource types", map[string]Quantity{"first": 10}, map[string]Quantity{"first": -5, "second": 15}, map[string]Quantity{"first": -5}},
		{"Min of positive resource and negative resource with extra resource types", map[string]Quantity{"first": -5, "second": 15}, map[string]Quantity{"first": 10}, map[string]Quantity{"first": -5, "second": 15}},
	}
	for _, tc := range testCases {
		var left *Resource
		var right *Resource
		var expected *Resource
		if tc.left != nil {
			left = NewResourceFromMap(tc.left)
		}
		if tc.right != nil {
			right = NewResourceFromMap(tc.right)
		}
		if tc.expected != nil {
			expected = NewResourceFromMap(tc.expected)
		}
		t.Run(tc.name, func(t *testing.T) {
			result := ComponentWiseMinOnlyExisting(left, right)
			assert.DeepEqual(t, result, expected)
		})
	}
}

func TestMergeIfNotPresent(t *testing.T) {
	testCases := []struct {
		name     string
		left     map[string]Quantity
		right    map[string]Quantity
		expected map[string]Quantity
	}{
		{"Min of nil resources should be nil", nil, nil, nil},
		{"Min of empty resources should be empty resource ", map[string]Quantity{}, map[string]Quantity{}, map[string]Quantity{}},
		{"Min of positive resource and nil resource", map[string]Quantity{"first": 5}, nil, map[string]Quantity{"first": 5}},
		{"Min of nil resource and positive resource", nil, map[string]Quantity{"first": 5}, map[string]Quantity{"first": 5}},
		{"Min of two positive resources", map[string]Quantity{"first": 5}, map[string]Quantity{"first": 10}, map[string]Quantity{"first": 5}},
		{"Min of two positive resources", map[string]Quantity{"first": 10}, map[string]Quantity{"first": 5}, map[string]Quantity{"first": 10}},
		{"Min of positive resource and negative resource", map[string]Quantity{"first": 5}, map[string]Quantity{"first": -5}, map[string]Quantity{"first": 5}},
		{"Min of positive resource and negative resource", map[string]Quantity{"first": -5}, map[string]Quantity{"first": 5}, map[string]Quantity{"first": -5}},
		{"Min of two positive resources with extra resource types", map[string]Quantity{"first": 10}, map[string]Quantity{"first": 5, "second": 15}, map[string]Quantity{"first": 10, "second": 15}},
		{"Min of two positive resources with extra resource types", map[string]Quantity{"first": 5, "second": 15}, map[string]Quantity{"first": 10}, map[string]Quantity{"first": 5, "second": 15}},
		{"Min of positive resource and negative resource with extra resource types", map[string]Quantity{"first": 10}, map[string]Quantity{"first": -5, "second": 15}, map[string]Quantity{"first": 10, "second": 15}},
		{"Min of positive resource and negative resource with extra resource types", map[string]Quantity{"first": -5, "second": 15}, map[string]Quantity{"first": 10}, map[string]Quantity{"first": -5, "second": 15}},
	}
	for _, tc := range testCases {
		var left *Resource
		var right *Resource
		var expected *Resource
		if tc.left != nil {
			left = NewResourceFromMap(tc.left)
		}
		if tc.right != nil {
			right = NewResourceFromMap(tc.right)
		}
		if tc.expected != nil {
			expected = NewResourceFromMap(tc.expected)
		}
		t.Run(tc.name, func(t *testing.T) {
			result := MergeIfNotPresent(left, right)
			assert.DeepEqual(t, result, expected)
		})
	}
}

func TestComponentWiseMax(t *testing.T) {
	type inputs struct {
		res1    map[string]Quantity
		res2    map[string]Quantity
		sameRef bool
	}
	var tests = []struct {
		caseName string
		input    inputs
		expected map[string]Quantity
	}{
		{"nil check", inputs{nil, make(map[string]Quantity), false}, make(map[string]Quantity)},
		{"nil check", inputs{make(map[string]Quantity), nil, false}, make(map[string]Quantity)},
		{"Same reference", inputs{make(map[string]Quantity), nil, true}, make(map[string]Quantity)},
		{"Empty resource and a resource including zero", inputs{make(map[string]Quantity), map[string]Quantity{"zero": 0}, false}, map[string]Quantity{"zero": 0}},
		{"Different resource type", inputs{map[string]Quantity{"first": 5}, map[string]Quantity{"second": 10}, false}, map[string]Quantity{"first": 5, "second": 10}},
		{"Choosing bigger one", inputs{map[string]Quantity{"first": 5}, map[string]Quantity{"first": 10}, false}, map[string]Quantity{"first": 10}},
		{"Choosing bigger one", inputs{map[string]Quantity{"first": 10}, map[string]Quantity{"first": 5}, false}, map[string]Quantity{"first": 10}},
		{"Negative resource", inputs{map[string]Quantity{"first": -5, "second": -5}, map[string]Quantity{"first": 10}, false}, map[string]Quantity{"first": 10, "second": 0}},
	}
	for _, tt := range tests {
		t.Run(tt.caseName, func(t *testing.T) {
			var res1, res2 *Resource
			if tt.input.res1 != nil {
				res1 = NewResourceFromMap(tt.input.res1)
			}
			if tt.input.sameRef {
				res2 = res1
			} else if tt.input.res2 != nil {
				res2 = NewResourceFromMap(tt.input.res2)
			}

			result := ComponentWiseMax(res1, res2)
			if result == nil {
				t.Error("Result should be a zero resource instead of nil")
			} else if len(result.Resources) != len(tt.expected) {
				t.Errorf("Length got %d, expected %d", len(result.Resources), len(tt.expected))
			}

			for expectedKey, expectedValue := range tt.expected {
				if value, ok := result.Resources[expectedKey]; ok {
					if value != expectedValue {
						t.Errorf("Value of %s is wrong, got %d, expected %d", expectedKey, value, expectedValue)
					}
				} else {
					t.Errorf("resource key %v is not set", expectedKey)
				}
			}
		})
	}
}

func TestToProtoNil(t *testing.T) {
	// make sure we're nil safe IDE will complain about the non nil check
	defer func() {
		if r := recover(); r != nil {
			t.Fatal("panic on nil resource in proto test")
		}
	}()
	var empty *Resource
	proto := empty.ToProto()
	if proto == nil || len(proto.Resources) != 0 {
		t.Fatalf("to proto on nil resource should have given non nil object")
	}
}

func TestToProto(t *testing.T) {
	var tests = []struct {
		caseName string
		input    map[string]Quantity
		expected int
	}{
		{"nil", nil, 0},
		{"empty resource", map[string]Quantity{}, 0},
		{"setting resource", map[string]Quantity{"first": 5, "second": -5}, 2},
		{"resource including 0", map[string]Quantity{"first": 5, "second": 0, "third": -5}, 3},
	}
	for _, tt := range tests {
		t.Run(tt.caseName, func(t *testing.T) {
			toProto := NewResourceFromMap(tt.input).ToProto()
			if toProto == nil {
				t.Error("ToProto should return a empty resource instead of nil")
			} else {
				if got := len(toProto.Resources); got != tt.expected {
					t.Errorf("Number of resource type: got %d, expected %d", got, tt.expected)
				}

				// Unexpected resource type
				for key := range toProto.Resources {
					if _, ok := tt.input[key]; !ok {
						t.Errorf("Resource type %s is not expected", key)
					}
				}

				// Checking the number of the resource
				for key, expected := range tt.input {
					if got, ok := toProto.Resources[key]; ok {
						if int64(expected) != got.Value {
							t.Errorf("%s value: got %d, expected %d", key, got.Value, int64(expected))
						}
					}
				}
			}
		})
	}
}

func TestNewResourceFromProto(t *testing.T) {
	var tests = []struct {
		caseName   string
		input      map[string]Quantity
		expected   int
		ProtoIsNil bool
	}{
		{"Proto is nil", nil, 0, true},
		{"nil resource", nil, 0, false},
		{"empty resource", map[string]Quantity{}, 0, false},
		{"setting resource", map[string]Quantity{"first": 5, "second": -5}, 2, false},
		{"resource including 0", map[string]Quantity{"first": 5, "second": 0, "third": -5}, 3, false},
	}
	for _, tt := range tests {
		t.Run(tt.caseName, func(t *testing.T) {
			res1 := NewResourceFromMap(tt.input)
			if ok, err := CheckLenOfResource(res1, tt.expected); !ok {
				t.Error(err)
			}

			res2 := NewResourceFromProto(res1.ToProto())
			if tt.ProtoIsNil {
				res2 = NewResourceFromProto(nil)
			}
			if ok, err := CheckLenOfResource(res2, tt.expected); !ok {
				t.Error(err)
			}
			if !reflect.DeepEqual(res1.Resources, res2.Resources) {
				t.Errorf("resource to proto and back to resource does not give same resources: original %v after %v", res1, res2)
			}
		})
	}
}

func TestMultiplyBy(t *testing.T) {
	// simple case (nil checks)
	result := MultiplyBy(nil, 0)
	if result == nil || len(result.Resources) != 0 {
		t.Errorf("nil resource (left) did not return zero resource: %v", result)
	}
	// simple case empty resource
	base := NewResourceFromMap(map[string]Quantity{})
	result = MultiplyBy(base, 1)
	if len(result.Resources) != 0 {
		t.Errorf("empty resource did not return zero resource: %v", result)
	}

	// zero multiply factor
	base = NewResourceFromMap(map[string]Quantity{"first": 5})
	result = MultiplyBy(base, 0)
	if len(result.Resources) != 0 {
		t.Errorf("zero factor did not return correct number of resource values: %v", result)
	}

	// fractional multiplier
	// positive multiplication factor
	base = NewResourceFromMap(map[string]Quantity{"first": 5, "second": -5})
	result = MultiplyBy(base, 1.9)
	if len(result.Resources) != 2 {
		t.Errorf("positive factor did not return correct number of resource values: %v", result)
	}
	if val, ok := result.Resources["first"]; !ok || val != 9 {
		t.Errorf("positive factor did not set first value correctly: %v", result)
	}
	if val, ok := result.Resources["second"]; !ok || val != -9 {
		t.Errorf("positive factor did not set second value correctly: %v", result)
	}
	// negative multiplication factor
	base = NewResourceFromMap(map[string]Quantity{"first": 5, "second": -5})
	result = MultiplyBy(base, -1.9)
	if len(result.Resources) != 2 {
		t.Errorf("negative factor did not return correct number of resource values: %v", result)
	}
	if val, ok := result.Resources["first"]; !ok || val != -9 {
		t.Errorf("negative factor did not set first value correctly: %v", result)
	}
	if val, ok := result.Resources["second"]; !ok || val != 9 {
		t.Errorf("negative factor did not set second value correctly: %v", result)
	}
}

func TestMultiply(t *testing.T) {
	// simple case (nil checks)
	result := Multiply(nil, 0)
	if result == nil || len(result.Resources) != 0 {
		t.Errorf("nil resource (left) did not return zero resource: %v", result)
	}
	// simple case empty resource
	base := NewResourceFromMap(map[string]Quantity{})
	result = Multiply(base, 1)
	if len(result.Resources) != 0 {
		t.Errorf("empty resource did not return zero resource: %v", result)
	}

	// zero multiply factor
	base = NewResourceFromMap(map[string]Quantity{"first": 5})
	result = Multiply(base, 0)
	if len(result.Resources) != 0 {
		t.Errorf("zero factor did not return no resource values: %v", result)
	}

	// positive multiplication factor
	base = NewResourceFromMap(map[string]Quantity{"first": 5, "second": -5})
	result = Multiply(base, 2)
	if len(result.Resources) != 2 {
		t.Errorf("positive factor did not return correct number of resource values: %v", result)
	}
	if val, ok := result.Resources["first"]; !ok || val != 10 {
		t.Errorf("positive factor did not set first value correctly: %v", result)
	}
	if val, ok := result.Resources["second"]; !ok || val != -10 {
		t.Errorf("positive factor did not set second value correctly: %v", result)
	}
	// negative multiplication factor
	base = NewResourceFromMap(map[string]Quantity{"first": 5, "second": -5})
	result = Multiply(base, -2)
	if len(result.Resources) != 2 {
		t.Errorf("negative factor did not return correct number of resource values: %v", result)
	}
	if val, ok := result.Resources["first"]; !ok || val != -10 {
		t.Errorf("negative factor did not set first value correctly: %v", result)
	}
	if val, ok := result.Resources["second"]; !ok || val != 10 {
		t.Errorf("negative factor did not set second value correctly: %v", result)
	}
}

func TestMultiplyToNil(t *testing.T) {
	// make sure we're nil safe IDE will complain about the non nil check
	defer func() {
		if r := recover(); r != nil {
			t.Fatal("panic on nil resource in multiplyto test")
		}
	}()
	var empty *Resource
	empty.MultiplyTo(0)
}

func TestMultiplyTo(t *testing.T) {
	// simple case empty resource
	base := NewResourceFromMap(map[string]Quantity{})
	base.MultiplyTo(1)
	if len(base.Resources) != 0 {
		t.Errorf("empty resource did not return zero resource: %v", base)
	}

	// zero multiply factor
	base = NewResourceFromMap(map[string]Quantity{"first": 5})
	base.MultiplyTo(0)
	if len(base.Resources) != 1 {
		t.Errorf("zero factor did not return correct number of resource values: %v", base)
	}
	if val, ok := base.Resources["first"]; !ok || val != 0 {
		t.Errorf("zero factor did not set value to zero: %v", base)
	}

	// fractional multiplier
	// positive multiplication factor
	base = NewResourceFromMap(map[string]Quantity{"first": 5, "second": -5})
	base.MultiplyTo(1.9)
	if len(base.Resources) != 2 {
		t.Errorf("positive factor did not return correct number of resource values: %v", base)
	}
	if val, ok := base.Resources["first"]; !ok || val != 9 {
		t.Errorf("positive factor did not set first value correctly: %v", base)
	}
	if val, ok := base.Resources["second"]; !ok || val != -9 {
		t.Errorf("positive factor did not set second value correctly: %v", base)
	}
	// negative multiplication factor
	base = NewResourceFromMap(map[string]Quantity{"first": 5, "second": -5})
	base.MultiplyTo(-1.9)
	if len(base.Resources) != 2 {
		t.Errorf("negative factor did not return correct number of resource values: %v", base)
	}
	if val, ok := base.Resources["first"]; !ok || val != -9 {
		t.Errorf("negative factor did not set first value correctly: %v", base)
	}
	if val, ok := base.Resources["second"]; !ok || val != 9 {
		t.Errorf("negative factor did not set second value correctly: %v", base)
	}
}

func TestWrapSafe(t *testing.T) {
	// additions and subtract use the same code
	if addVal(math.MaxInt64, 1) != math.MaxInt64 {
		t.Error("MaxInt64 + 1 != MaxInt64")
	}
	if addVal(math.MinInt64, -1) != math.MinInt64 {
		t.Error("MinInt64 + (-1) != MinInt64")
	}
	if addVal(10, 10) != 20 {
		t.Error("10 + 10 != 20")
	}
	if addVal(10, -20) != -10 {
		t.Error("10 + (-20) != -10")
	}
	if addVal(-10, 20) != 10 {
		t.Error("-10 + 20 != 10")
	}
	if addVal(-20, 10) != -10 {
		t.Error("-20 + 10 != -10")
	}
	if addVal(20, -10) != 10 {
		t.Error("20 + (-10) != 10")
	}
	// subtract special cases
	if subVal(math.MinInt64, 1) != math.MinInt64 {
		t.Error("MinInt64 - 1 != MinInt64")
	}
	if subVal(math.MaxInt64, -1) != math.MaxInt64 {
		t.Error("MaxInt64 - (-1) != MaxInt64")
	}

	// multiplications
	if mulVal(0, 0) != 0 {
		t.Error("0 * 0 != 0")
	}
	if mulVal(math.MaxInt64, -1) != math.MinInt64+1 {
		t.Error("MaxInt64 * -1 != MinInt64 + 1")
	}
	if mulVal(math.MinInt64, -1) != math.MaxInt64 {
		t.Error("MinInt64 * -1 != MaxInt64")
	}
	if mulVal(100000000000000000, -2000) != math.MinInt64 {
		t.Error("100000000000000000 * -2000 != MinInt64")
	}
	// strange one this returns -4 without checks
	if mulVal(math.MaxInt64/2, 4) != math.MaxInt64 {
		t.Error("math.MaxInt64/2 * 4 != MaxInt64")
	}

	// large base
	if mulValRatio(math.MaxInt64, 2) != math.MaxInt64 {
		t.Error("float MaxInt64 * 2 != MaxInt64")
	}
	// small base
	if mulValRatio(math.MinInt64, 2) != math.MinInt64 {
		t.Error("float MinInt64 * 2 != MinInt64")
	}
}

func TestAdd(t *testing.T) {
	// simple case (nil checks)
	result := Add(nil, nil)
	if result == nil || len(result.Resources) != 0 {
		t.Errorf("add nil resources did not return zero resource: %v", result)
	}
	// empty resources
	left := Zero
	result = Add(left, nil)
	if result == nil || len(result.Resources) != 0 || result == left {
		t.Errorf("add Zero resource (right) did not return cloned resource: %v", result)
	}

	// simple empty resources
	res1 := NewResourceFromMap(map[string]Quantity{"a": 5})
	result = Add(left, res1)
	if result == nil || len(result.Resources) != 1 || result.Resources["a"] != 5 {
		t.Errorf("add simple resource did not return cloned input resource: %v", result)
	}

	// complex case: just checking the resource merge, values check is secondary
	res1 = &Resource{Resources: map[string]Quantity{"a": 0, "b": 1}}
	res2 := &Resource{Resources: map[string]Quantity{"a": 1, "c": 0, "d": -1}}
	res3 := Add(res1, res2)

	expected := map[string]Quantity{"a": 1, "b": 1, "c": 0, "d": -1}
	if !reflect.DeepEqual(res3.Resources, expected) {
		t.Errorf("add failed expected %v, actual %v", expected, res3.Resources)
	}
}

func TestAddToNil(t *testing.T) {
	// make sure we're nil safe IDE will complain about the non nil check
	defer func() {
		if r := recover(); r != nil {
			t.Fatal("panic on nil resource in addto test")
		}
	}()
	var empty *Resource
	empty.AddTo(NewResource())
}

func TestAddTo(t *testing.T) {
	// simple case (nil checks) & empty resources
	base := NewResource()
	base.AddTo(nil)
	if len(base.Resources) != 0 {
		t.Errorf("addto nil resource modified base resource: %v", base)
	}

	// simple resources
	res1 := NewResourceFromMap(map[string]Quantity{"a": 5})
	base.AddTo(res1)
	if len(base.Resources) != 1 && base.Resources["a"] != 5 {
		t.Errorf("addto simple resource did not return merged input resource: %v", base)
	}

	// complex case: just checking the resource merge, values check is secondary
	base = &Resource{Resources: map[string]Quantity{"a": 0, "b": 1}}
	res1 = &Resource{Resources: map[string]Quantity{"a": 1, "c": 0, "d": -1}}
	base.AddTo(res1)

	expected := map[string]Quantity{"a": 1, "b": 1, "c": 0, "d": -1}
	if !reflect.DeepEqual(base.Resources, expected) {
		t.Errorf("addto failed expected %v, actual %v", expected, base.Resources)
	}
}

func TestSub(t *testing.T) {
	// simple case (nil checks)
	result := Sub(nil, nil)
	if result == nil || len(result.Resources) != 0 {
		t.Errorf("sub nil resources did not return zero resource: %v", result)
	}
	// empty resources
	left := NewResource()
	result = Sub(left, nil)
	if result == nil || len(result.Resources) != 0 || result == left {
		t.Errorf("sub Zero resource (right) did not return cloned resource: %v", result)
	}

	// simple empty resources
	res1 := NewResourceFromMap(map[string]Quantity{"a": 5})
	result = Sub(left, res1)
	if result == nil || len(result.Resources) != 1 || result.Resources["a"] != -5 {
		t.Errorf("sub simple resource did not return correct resource: %v", result)
	}

	// complex case: just checking the resource merge, values check is secondary
	res1 = &Resource{Resources: map[string]Quantity{"a": 0, "b": 1}}
	res2 := &Resource{Resources: map[string]Quantity{"a": 1, "c": 0, "d": -1}}
	res3 := Sub(res1, res2)

	expected := map[string]Quantity{"a": -1, "b": 1, "c": 0, "d": 1}
	if !reflect.DeepEqual(res3.Resources, expected) {
		t.Errorf("Add failed expected %v, actual %v", expected, res3.Resources)
	}
}

func TestSubFromNil(t *testing.T) {
	// make sure we're nil safe IDE will complain about the non nil check
	defer func() {
		if r := recover(); r != nil {
			t.Fatal("panic on nil resource in subfrom test")
		}
	}()
	var empty *Resource
	empty.SubFrom(NewResource())
}

func TestSubFrom(t *testing.T) {
	// simple case (nil checks) & empty resources
	base := NewResource()
	base.SubFrom(nil)
	if len(base.Resources) != 0 {
		t.Errorf("subfrom nil resource modified base resource: %v", base)
	}

	// simple resources
	res1 := NewResourceFromMap(map[string]Quantity{"a": 5})
	base.SubFrom(res1)
	if len(base.Resources) != 1 && base.Resources["a"] != -5 {
		t.Errorf("subfrom simple resource did not return merged input resource: %v", base)
	}

	// complex case: just checking the resource merge, values check is secondary
	base = &Resource{Resources: map[string]Quantity{"a": 0, "b": 1}}
	res1 = &Resource{Resources: map[string]Quantity{"a": 1, "c": 0, "d": -1}}
	base.SubFrom(res1)

	expected := map[string]Quantity{"a": -1, "b": 1, "c": 0, "d": 1}
	if !reflect.DeepEqual(base.Resources, expected) {
		t.Errorf("subfrom failed expected %v, actual %v", expected, base.Resources)
	}
}

func TestSubEliminateNegative(t *testing.T) {
	// simple case (nil checks)
	result := SubEliminateNegative(nil, nil)
	if result == nil || len(result.Resources) != 0 {
		t.Errorf("sub nil resources did not return zero resource: %v", result)
	}
}

func TestSubOnlyExisting(t *testing.T) {
	var tests = []struct {
		caseName string
		base     map[string]Quantity
		delta    map[string]Quantity
		expected map[string]Quantity
	}{
		{"nil resources", nil, nil, nil},
		{"nil base", nil, map[string]Quantity{"a": 5}, nil},
		{"nil delta", map[string]Quantity{"a": 5}, nil, map[string]Quantity{"a": 5}},
		{"empty base", map[string]Quantity{}, map[string]Quantity{"a": 5}, map[string]Quantity{}},
		{"single type base matched", map[string]Quantity{"a": 5}, map[string]Quantity{"a": 3, "b": 1}, map[string]Quantity{"a": 2}},
		{"single type base unmatched", map[string]Quantity{"a": 5}, map[string]Quantity{"b": 1}, map[string]Quantity{"a": 5}},
		{"multi type base partial match", map[string]Quantity{"a": 5, "b": 3}, map[string]Quantity{"a": 2, "c": 1}, map[string]Quantity{"a": 3, "b": 3}},
		{"multi type base match", map[string]Quantity{"a": 5, "b": 3}, map[string]Quantity{"a": 2, "b": 1}, map[string]Quantity{"a": 3, "b": 2}},
		{"negative result", map[string]Quantity{"a": 1}, map[string]Quantity{"a": 3}, map[string]Quantity{"a": -2}},
	}
	for _, tt := range tests {
		t.Run(tt.caseName, func(t *testing.T) {
			var base, delta, expected *Resource
			if tt.base != nil {
				base = NewResourceFromMap(tt.base)
			}
			if tt.delta != nil {
				delta = NewResourceFromMap(tt.delta)
			}
			if tt.expected != nil {
				expected = NewResourceFromMap(tt.expected)
			}
			result := SubOnlyExisting(base, delta)
			assert.Assert(t, Equals(result, expected), "incorrect result returned")
		})
	}
}

func TestAddOnlyExisting(t *testing.T) {
	var tests = []struct {
		caseName string
		base     map[string]Quantity
		delta    map[string]Quantity
		expected map[string]Quantity
	}{
		{"nil resources", nil, nil, nil},
		{"nil base", nil, map[string]Quantity{"a": 5}, nil},
		{"nil delta", map[string]Quantity{"a": 5}, nil, map[string]Quantity{"a": 5}},
		{"empty base", map[string]Quantity{}, map[string]Quantity{"a": 5}, map[string]Quantity{}},
		{"single type base matched", map[string]Quantity{"a": 0}, map[string]Quantity{"a": 1, "b": 1}, map[string]Quantity{"a": 1}},
		{"single type base unmatched", map[string]Quantity{"a": 0}, map[string]Quantity{"b": 1}, map[string]Quantity{"a": 0}},
		{"multi type base partial match", map[string]Quantity{"a": 1, "b": 0}, map[string]Quantity{"a": 1, "c": 10}, map[string]Quantity{"a": 2, "b": 0}},
		{"multi type base match", map[string]Quantity{"a": 1, "b": 0}, map[string]Quantity{"a": 1, "b": 10}, map[string]Quantity{"a": 2, "b": 10}},
	}
	for _, tt := range tests {
		t.Run(tt.caseName, func(t *testing.T) {
			var base, delta, expected *Resource
			if tt.base != nil {
				base = NewResourceFromMap(tt.base)
			}
			if tt.delta != nil {
				delta = NewResourceFromMap(tt.delta)
			}
			if tt.expected != nil {
				expected = NewResourceFromMap(tt.expected)
			}
			result := AddOnlyExisting(base, delta)
			assert.Assert(t, Equals(result, expected), "incorrect result returned")
		})
	}
}

func TestSubErrorNegative(t *testing.T) {
	// simple case (nil checks)
	result, err := SubErrorNegative(nil, nil)
	if err != nil || result == nil || len(result.Resources) != 0 {
		t.Errorf("sub nil resources did not return zero resource: %v, %v", result, err)
	}
	// empty resources
	left := NewResource()
	result, err = SubErrorNegative(left, nil)
	if err != nil || result == nil || len(result.Resources) != 0 || result == left {
		t.Errorf("sub Zero resource (right) did not return cloned resource: %v, %v", result, err)
	}

	// simple empty resources
	res1 := NewResourceFromMap(map[string]Quantity{"a": 5})
	result, err = SubErrorNegative(left, res1)
	if err == nil || result == nil || len(result.Resources) != 1 || result.Resources["a"] != 0 {
		t.Errorf("sub simple resource did not return correct resource or no error: %v, %v", result, err)
	}

	// complex case: just checking the resource merge, values check is secondary
	res1 = &Resource{Resources: map[string]Quantity{"a": 0, "b": 1}}
	res2 := &Resource{Resources: map[string]Quantity{"a": 1, "c": 1, "d": -1}}
	result, err = SubErrorNegative(res1, res2)
	if err == nil {
		t.Errorf("sub should have set error message and did not: %v", result)
	}

	expected := map[string]Quantity{"a": 0, "b": 1, "c": 0, "d": 1}
	if !reflect.DeepEqual(result.Resources, expected) {
		t.Errorf("sub failed expected %v, actual %v", expected, result.Resources)
	}
}

func TestEqualsOrEmpty(t *testing.T) {
	var tests = []struct {
		left, right *Resource
		want        bool
	}{
		{nil, nil, true},
		{nil, NewResourceFromMap(map[string]Quantity{"a": 0, "b": 1}), false},
		{NewResourceFromMap(map[string]Quantity{"a": 0, "b": 1}), nil, false},
		{nil, NewResource(), true},
		{NewResource(), nil, true},
		{NewResourceFromMap(map[string]Quantity{"a": 0, "b": 1}), NewResourceFromMap(map[string]Quantity{"a": 0, "b": 1}), true},
		{NewResourceFromMap(map[string]Quantity{"a": 0, "c": 1}), NewResourceFromMap(map[string]Quantity{"a": 0, "d": 3}), false},
	}

	for _, tt := range tests {
		if got := EqualsOrEmpty(tt.left, tt.right); got != tt.want {
			t.Errorf("got %v, want %v", got, tt.want)
		}
	}
}

func TestDeepEquals(t *testing.T) {
	var tests = []struct {
		left, right *Resource
		want        bool
	}{
		{nil, nil, true},
		{nil, NewResourceFromMap(map[string]Quantity{"a": 0, "b": 1}), false},
		{NewResourceFromMap(map[string]Quantity{"a": 0, "b": 1}), nil, false},
		{nil, NewResource(), false},
		{NewResource(), nil, false},
		{NewResourceFromMap(map[string]Quantity{"a": 0}), NewResourceFromMap(map[string]Quantity{"a": 0}), true},
		{NewResourceFromMap(map[string]Quantity{"a": 0, "b": 1}), NewResourceFromMap(map[string]Quantity{"a": 0, "b": 1}), true},
		{NewResourceFromMap(map[string]Quantity{"a": 0}), NewResourceFromMap(map[string]Quantity{"b": 0, "c": 1}), false},
		{NewResourceFromMap(map[string]Quantity{"a": 0, "b": 1}), NewResourceFromMap(map[string]Quantity{"a": 1, "b": 1}), false},
		{NewResourceFromMap(map[string]Quantity{"a": 0, "c": 1}), NewResourceFromMap(map[string]Quantity{"a": 0, "d": 3}), false},
		{NewResourceFromMap(map[string]Quantity{"a": 0}), NewResourceFromMap(map[string]Quantity{"d": 0}), false},
	}

	for _, tt := range tests {
		if got := DeepEquals(tt.left, tt.right); got != tt.want {
			t.Errorf("got %v, want %v", got, tt.want)
		}
	}
}

func TestFitIn(t *testing.T) {
	tests := []struct {
		name    string
		larger  *Resource
		smaller *Resource
		want    bool
	}{
		{"nil larger", nil, NewResource(), true},
		{"nil smaller", NewResource(), nil, true},
		{"zero set", NewResource(), NewResourceFromMap(map[string]Quantity{"a": 1}), false},
		{"same type", NewResourceFromMap(map[string]Quantity{"a": 5}), NewResourceFromMap(map[string]Quantity{"a": 1}), true},
		{"not in smaller", NewResourceFromMap(map[string]Quantity{"not-in-smaller": 1}), NewResourceFromMap(map[string]Quantity{"a": 1}), false},
		{"not in larger", NewResourceFromMap(map[string]Quantity{"not-in-smaller": 1}), NewResourceFromMap(map[string]Quantity{"not-in-larger": 1}), false},
		{"negative larger", NewResourceFromMap(map[string]Quantity{"a": -10}), NewResourceFromMap(map[string]Quantity{"a": 0, "b": -10}), true},
		{"negative smaller", NewResourceFromMap(map[string]Quantity{"a": -5}), NewResourceFromMap(map[string]Quantity{"a": 0, "b": 10}), false},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.larger.FitIn(tt.smaller), tt.want, "unexpected FitIn result (parameter method)")
			assert.Equal(t, tt.larger.FitIn(tt.smaller), tt.want, "unexpected FitIn result (receiver method)")
		})
	}
}

// simple cases (nil checks)
func TestFinInNil(t *testing.T) {
	defer func() {
		if r := recover(); r != nil {
			t.Fatal("FitIn panic on nil resource")
		}
	}()
	var res *Resource
	// ignore nil check from IDE we really want to do this
	assert.Assert(t, res.FitInMaxUndef(nil), "fitin nil should fit in nil (skip undefined)")
	assert.Assert(t, res.FitInMaxUndef(NewResource()), "fitin nil should fit in empty resource failed (skip undefined)")
	smaller := &Resource{Resources: map[string]Quantity{"a": 1}}
	assert.Assert(t, res.FitInMaxUndef(smaller), "defined resource should fit in nil resource (skip undefined)")
}

func TestFitInSkip(t *testing.T) {
	tests := []struct {
		larger   *Resource
		smaller  *Resource
		expected bool
		message  string
	}{
		{
			larger:   NewResource(),
			smaller:  &Resource{Resources: map[string]Quantity{"a": 1}},
			expected: true,
			message:  "defined resource %+v should fit in empty (skip undefined)",
		},
		{
			larger:   NewResourceFromMap(map[string]Quantity{"a": 5}),
			smaller:  &Resource{Resources: map[string]Quantity{"a": 1}},
			expected: true,
			message:  "fitin smaller resource with value %+v should fit in larger %+v (skip undefined)",
		},
		{
			smaller:  &Resource{Resources: map[string]Quantity{"a": 1}},
			larger:   &Resource{Resources: map[string]Quantity{"not-in-smaller": 1}},
			expected: true,
			message:  "different type in smaller %+v should fit in larger %+v (skip undefined)",
		},
		{
			larger:   &Resource{Resources: map[string]Quantity{"not-in-smaller": 1}},
			smaller:  &Resource{Resources: map[string]Quantity{"not-in-larger": 1}},
			expected: true,
			message:  "different type in smaller %+v should fit in larger %+v (skip undefined)",
		},
		{
			larger:   &Resource{Resources: map[string]Quantity{"a": -10}},
			smaller:  &Resource{Resources: map[string]Quantity{"a": 0, "b": -10}},
			expected: true,
			message:  "fitin smaller resource with zero or neg values %+v should fit in larger %+v (skip undefined)",
		},
		{
			larger:   &Resource{Resources: map[string]Quantity{"a": -5}},
			smaller:  &Resource{Resources: map[string]Quantity{"a": 0, "b": 10}},
			expected: true,
			message:  "fitin smaller resource with value %+v should fit in larger %+v (skip undefined)",
		},
	}

	for _, tc := range tests {
		t.Run(tc.message, func(t *testing.T) {
			result := tc.larger.FitInMaxUndef(tc.smaller)
			assert.Equal(t, result, tc.expected, tc.message, tc.smaller, tc.larger)
		})
	}
}

//nolint:funlen // thorough test
func TestGetFairShare(t *testing.T) {
	// 0 guarantee should be treated as absence of a gurantee
	// test to protect against division by 0. full=0. rare but possible.
	tests := []struct {
		allocated  *Resource
		guaranteed *Resource
		fairmax    *Resource
		expected   float64
	}{
		// guarantees exist for each resource type so full does not come into play
		{
			allocated:  &Resource{Resources: map[string]Quantity{"vcores": 0}},
			guaranteed: &Resource{Resources: map[string]Quantity{"vcores": 1000}},
			fairmax:    &Resource{Resources: map[string]Quantity{"vcores": 99999}},
			expected:   float64(0.0) / float64(1000.0),
		},
		{
			allocated:  &Resource{Resources: map[string]Quantity{"vcores": 100, "memory": 2500}},
			guaranteed: &Resource{Resources: map[string]Quantity{"vcores": 1000, "memory": 5000}},
			fairmax:    &Resource{Resources: map[string]Quantity{"vcores": 99999, "memory": 99999}},
			expected:   float64(2500) / float64(5000),
		},
		{
			allocated:  &Resource{Resources: map[string]Quantity{"vcores": 100, "memory": 2500, "ephemeral-storage": 100}},
			guaranteed: &Resource{Resources: map[string]Quantity{"vcores": 1000, "memory": 5000, "ephemeral-storage": 2000}},
			fairmax:    &Resource{Resources: map[string]Quantity{"vcores": 99999, "memory": 99999, "ephemeral-storage": 99999}},
			expected:   float64(2500) / float64(5000),
		},

		// in the absence of guarantees the share denominator will be determined by the full value
		{ // ephemeral-storage = 1000 / 2000 = 0.5; fairmax ephemeral-stagoe dominates
			allocated:  &Resource{Resources: map[string]Quantity{"ephemeral-storage": 1000, "memory": 1000, "pods": 1000, "vcore": 1000}},
			guaranteed: &Resource{Resources: map[string]Quantity{}},
			fairmax:    &Resource{Resources: map[string]Quantity{"ephemeral-storage": 2000, "memory": 99999, "pods": 99999, "vcore": 99999}},
			expected:   float64(1000) / float64(2000),
		},

		// there is a guarantee on pods but fairmax ephemeral-storage still dominates
		{ // ephemeral-storage = 0.5, memory = 1000 / 2000; fairmax ephemeral-stagoe dominates
			allocated:  &Resource{Resources: map[string]Quantity{"ephemeral-storage": 1000, "memory": 1000, "pods": 1000, "vcore": 1000}},
			guaranteed: &Resource{Resources: map[string]Quantity{"pods": 88888}},
			fairmax:    &Resource{Resources: map[string]Quantity{"ephemeral-storage": 2000, "memory": 99999, "pods": 99999, "vcore": 99999}},
			expected:   float64(1000) / float64(2000),
		},

		// guarantee on vcores  but it still dominates all fairmax resources
		{ // vcores = 1000 / 2000; gauranteed vcores dominates
			allocated:  &Resource{Resources: map[string]Quantity{"ephemeral-storage": 1000, "memory": 1000, "pods": 1000, "vcore": 1000}},
			guaranteed: &Resource{Resources: map[string]Quantity{"vcore": 2000}},
			fairmax:    &Resource{Resources: map[string]Quantity{"ephemeral-storage": 99999, "memory": 99999, "pods": 99999, "vcore": 99999}},
			expected:   float64(1000) / float64(2000),
		},

		// 0 allocated
		{ // ephemeral-storage = 0 / 2000;
			allocated:  &Resource{Resources: map[string]Quantity{"ephemeral-storage": 0, "memory": 0, "pods": 0, "vcore": 0}},
			guaranteed: &Resource{Resources: map[string]Quantity{"vcores": 2000}},
			fairmax:    &Resource{Resources: map[string]Quantity{"ephemeral-storage": 99999, "memory": 99999, "pods": 99999, "vcore": 99999}},
			expected:   float64(0) / float64(2000),
		},

		// explicit 0 gurantee with usage
		{ // vcore has usage, therefore share is 1.0(100%)
			allocated:  &Resource{Resources: map[string]Quantity{"ephemeral-storage": 1000, "memory": 1000, "pods": 1000, "vcore": 10}},
			guaranteed: &Resource{Resources: map[string]Quantity{"vcore": 0}},
			fairmax:    &Resource{Resources: map[string]Quantity{"ephemeral-storage": 5000, "memory": 5000, "pods": 5000, "vcore": 5000}},
			expected:   float64(1.0),
		},

		// explicit 0 gurantee with NO usage
		{ // vcore has NO usage, so it's share is 0. memory = 1000 / 5000 = 0.20
			allocated:  &Resource{Resources: map[string]Quantity{"ephemeral-storage": 1000, "memory": 1000, "pods": 1000, "vcore": 0}},
			guaranteed: &Resource{Resources: map[string]Quantity{"vcore": 0}},
			fairmax:    &Resource{Resources: map[string]Quantity{"ephemeral-storage": 5000, "memory": 5000, "pods": 5000, "vcore": 5000}},
			expected:   float64(1000) / float64(5000),
		},

		// explicit 0 fairmax with usage
		{ // memory has explicity fairmax of 0 and usage.  It's share of 1.0 will dominate.
			allocated:  &Resource{Resources: map[string]Quantity{"ephemeral-storage": 1000, "memory": 1000, "pods": 1000, "vcore": 1000}},
			guaranteed: &Resource{Resources: map[string]Quantity{}},
			fairmax:    &Resource{Resources: map[string]Quantity{"ephemeral-storage": 5000, "memory": 0, "pods": 5000, "vcore": 5000}},
			expected:   float64(1.0),
		},

		// explicit 0 fairmax with NO usage
		{ // memory has explicit fairmax of 0 but no usage so it's share will be 0.  vcore dominates with 1000 / 4000 = 0.25
			allocated:  &Resource{Resources: map[string]Quantity{"ephemeral-storage": 1000, "memory": 0, "pods": 1000, "vcore": 1000}},
			guaranteed: &Resource{Resources: map[string]Quantity{}},
			fairmax:    &Resource{Resources: map[string]Quantity{"ephemeral-storage": 5000, "memory": 0, "pods": 5000, "vcore": 4000}},
			expected:   float64(1000) / float64(4000),
		},

		// negative gaurantee with usage
		{ // negative guarantee with usage on vcore = 1.0
			allocated:  &Resource{Resources: map[string]Quantity{"ephemeral-storage": 1000, "memory": 1000, "pods": 1000, "vcore": 1000}},
			guaranteed: &Resource{Resources: map[string]Quantity{"vcore": -10}},
			fairmax:    &Resource{Resources: map[string]Quantity{"ephemeral-storage": 5000, "memory": 5000, "pods": 5000, "vcore": 5000}},
			expected:   float64(1.0),
		},

		// negative guaranteed with NO usage
		{ // negative guarantee with NO usage on vcore = 0.0.  memory dominates with 1000 / 2000 = 0.5
			allocated:  &Resource{Resources: map[string]Quantity{"ephemeral-storage": 1000, "memory": 1000, "pods": 1000, "vcore": 0}},
			guaranteed: &Resource{Resources: map[string]Quantity{"vcore": -10}},
			fairmax:    &Resource{Resources: map[string]Quantity{"ephemeral-storage": 5000, "memory": 2000, "pods": 5000, "vcore": 5000}},
			expected:   float64(1000) / float64(2000),
		},

		// negative fairmax with usage
		{ // vcores = 1000 / -1000 = -1.  share is dominated by memory = 1000 / 5000 = 0.20
			allocated:  &Resource{Resources: map[string]Quantity{"ephemeral-storage": 1000, "memory": 1000, "pods": 1000, "vcore": 1000}},
			guaranteed: &Resource{Resources: map[string]Quantity{}},
			fairmax:    &Resource{Resources: map[string]Quantity{"ephemeral-storage": 5000, "memory": 5000, "pods": 5000, "vcore": -1000}},
			expected:   float64(1.0),
		},

		// negative fairmax with NO usage
		{ // vcores = 1000 / -1000 = -1.  share is dominated by memory = 1000 / 5000 = 0.20
			allocated:  &Resource{Resources: map[string]Quantity{"ephemeral-storage": 1000, "memory": 1000, "pods": 1000, "vcore": 0}},
			guaranteed: &Resource{Resources: map[string]Quantity{}},
			fairmax:    &Resource{Resources: map[string]Quantity{"ephemeral-storage": 5000, "memory": 5000, "pods": 5000, "vcore": -1000}},
			expected:   float64(1000) / float64(5000),
		},

		// negative usage gets no share
		{ // vcores = -1000 / 1000 = -1.  share is dominated by memory = 1000 / 5000 = 0.20
			allocated:  &Resource{Resources: map[string]Quantity{"ephemeral-storage": 1000, "memory": 1000, "pods": 1000, "vcore": -1000}},
			guaranteed: &Resource{Resources: map[string]Quantity{}},
			fairmax:    &Resource{Resources: map[string]Quantity{"ephemeral-storage": 5000, "memory": 5000, "pods": 5000, "vcore": 1000}},
			expected:   float64(1000) / float64(5000),
		},

		// nil guarantees are ignored.
		{
			allocated:  &Resource{Resources: map[string]Quantity{"ephemeral-storage": 1000, "memory": 1000, "pods": 1000, "vcore": 1000}},
			guaranteed: nil,
			fairmax:    &Resource{Resources: map[string]Quantity{"ephemeral-storage": 1000, "memory": 1000, "pods": 1000, "vcore": 1000}},
			expected:   float64(1.0),
		},

		// nil usage
		{
			allocated:  nil,
			guaranteed: &Resource{Resources: map[string]Quantity{}},
			fairmax:    &Resource{Resources: map[string]Quantity{"ephemeral-storage": 1000, "memory": 1000, "pods": 1000, "vcore": 1000}},
			expected:   float64(0.0),
		},
	}

	for _, tc := range tests {
		subtest := fmt.Sprintf("%s-%s-%s", tc.allocated, tc.guaranteed, tc.fairmax)
		t.Run(subtest, func(t *testing.T) {
			share := getFairShare(tc.allocated, tc.guaranteed, tc.fairmax)
			if !reflect.DeepEqual(share, tc.expected) {
				t.Errorf("incorrect share for allocated( %s ), guaranteed( %s ), fairmax( %s ) expected %v got: %v", tc.allocated, tc.guaranteed, tc.fairmax, tc.expected, share)
			}
		})
	}
}

func TestGetShares(t *testing.T) {
	tests := []struct {
		res      *Resource
		total    *Resource
		expected []float64
		message  string
	}{
		{
			res:      nil,
			total:    nil,
			expected: []float64{},
			message:  "nil resources",
		},
		{
			res:      NewResource(),
			total:    nil,
			expected: []float64{},
			message:  "empty resource with total nil",
		},
		{
			res:      NewResource(),
			total:    NewResource(),
			expected: []float64{},
			message:  "empty resources",
		},
		{
			res:      &Resource{Resources: map[string]Quantity{"zero": 0}},
			total:    nil,
			expected: []float64{0},
			message:  "zero valued resource",
		},
		{
			res:      &Resource{Resources: map[string]Quantity{"large": 5, "zero": 0, "small": -5}},
			total:    nil,
			expected: []float64{-5, 0, 5},
			message:  "negative valued resource",
		},
		{
			res:      &Resource{Resources: map[string]Quantity{"zero": 0}},
			total:    NewResource(),
			expected: []float64{0},
			message:  "zero valued resource with total",
		},
		{
			res:      &Resource{Resources: map[string]Quantity{"large": 5, "zero": 0, "small": -5}},
			total:    &Resource{Resources: map[string]Quantity{"large": 5, "zero": 0, "small": -5}},
			expected: []float64{0, 1, 1},
			message:  "same resource and total",
		},
		{
			res:      &Resource{Resources: map[string]Quantity{"large": 5, "zero": 0, "small": -5}},
			total:    &Resource{Resources: map[string]Quantity{"large": 10, "zero": 10, "small": 10}},
			expected: []float64{-0.5, 0, 0.5},
			message:  "negative share set to 0",
		},
		{
			res:      &Resource{Resources: map[string]Quantity{"large": 10, "small": 15}},
			total:    &Resource{Resources: map[string]Quantity{"large": 15, "small": 10}},
			expected: []float64{10.0 / 15.0, 1.5},
			message:  "resource larger than total",
		},
		{
			res:      &Resource{Resources: map[string]Quantity{"large": 5, "notintotal": 10}},
			total:    &Resource{Resources: map[string]Quantity{"large": 15}},
			expected: []float64{5.0 / 15.0, 10},
			message:  "resource not in total",
		},
	}

	for _, tc := range tests {
		t.Run(tc.message, func(t *testing.T) {
			shares := GetShares(tc.res, tc.total)
			if !reflect.DeepEqual(shares, tc.expected) {
				t.Errorf("incorrect shares for %s, expected %v got: %v", tc.message, tc.expected, shares)
			}
		})
	}
}

func TestCompUsageRatio(t *testing.T) {
	tests := []struct {
		left     *Resource
		right    *Resource
		total    *Resource
		expected int
		message  string
	}{
		{
			left:     nil,
			right:    nil,
			total:    nil,
			expected: 0,
			message:  "nil resources",
		},
		{
			left:     NewResource(),
			right:    NewResource(),
			total:    nil,
			expected: 0,
			message:  "empty resource with total nil",
		},
		{
			left:     NewResource(),
			right:    NewResource(),
			total:    NewResource(),
			expected: 0,
			message:  "empty resources",
		},
		{
			left:     NewResourceFromMap(map[string]Quantity{"zero": 0}),
			right:    NewResourceFromMap(map[string]Quantity{"zero": 0}),
			total:    nil,
			expected: 0,
			message:  "zero valued resource",
		},
		{
			left:     NewResourceFromMap(map[string]Quantity{"large": 5, "zero": 0, "small": -5}),
			right:    NewResourceFromMap(map[string]Quantity{"large": 5, "zero": 0, "small": -5}),
			total:    nil,
			expected: 0,
			message:  "negative valued resource",
		},
		{
			left:     NewResourceFromMap(map[string]Quantity{"zero": 0}),
			right:    NewResourceFromMap(map[string]Quantity{"zero": 0}),
			total:    NewResource(),
			expected: 0,
			message:  "zero valued resource with total",
		},
		{
			left:     NewResourceFromMap(map[string]Quantity{"large": 5, "zero": 0, "small": -5}),
			right:    NewResourceFromMap(map[string]Quantity{"large": 5, "zero": 0, "small": -5}),
			total:    NewResourceFromMap(map[string]Quantity{"large": 5, "zero": 0, "small": -5}),
			expected: 0,
			message:  "same resource and total",
		},
		{
			left:     NewResourceFromMap(map[string]Quantity{"large": 5, "zero": 0, "small": -5}),
			right:    NewResourceFromMap(map[string]Quantity{"large": 5, "zero": 0}),
			total:    NewResourceFromMap(map[string]Quantity{"large": 10, "zero": 10}),
			expected: -1,
			message:  "left side has more one negative value",
		},
		{
			left:     NewResourceFromMap(map[string]Quantity{"large": 5, "zero": 0}),
			right:    NewResourceFromMap(map[string]Quantity{"large": 5, "zero": 0, "small": -5}),
			total:    NewResourceFromMap(map[string]Quantity{"large": 10, "zero": 10}),
			expected: 1,
			message:  "right side has more one negative value",
		},
		{
			left:     NewResourceFromMap(map[string]Quantity{"first": 10, "second": 5}),
			right:    NewResourceFromMap(map[string]Quantity{"first": 5, "second": 10}),
			total:    NewResourceFromMap(map[string]Quantity{"first": 15}),
			expected: -1,
			message:  "left side first one bigger, last one smaller",
		},
		{
			left:     NewResourceFromMap(map[string]Quantity{"first": 5, "second": 10}),
			right:    NewResourceFromMap(map[string]Quantity{"first": 10, "second": 5}),
			total:    NewResourceFromMap(map[string]Quantity{"first": 15}),
			expected: 1,
			message:  "left side first one smaller, last one bigger",
		},
		{
			left:     NewResourceFromMap(map[string]Quantity{"second": 10, "first": 5}),
			right:    NewResourceFromMap(map[string]Quantity{"first": 10, "second": 5}),
			total:    NewResourceFromMap(map[string]Quantity{}),
			expected: 0,
			message:  "left side key order not same as right side",
		},
	}

	for _, tc := range tests {
		t.Run(tc.message, func(t *testing.T) {
			ratio := CompUsageRatio(tc.left, tc.right, tc.total)
			if ratio != tc.expected {
				t.Errorf("incorrect ratio for %s, expected %v got: %v", tc.message, tc.expected, ratio)
			}
		})
	}
}

func TestCompareShares(t *testing.T) {
	tests := []struct {
		left     []float64
		right    []float64
		expected int
		message  string
	}{
		{
			left:     nil,
			right:    nil,
			expected: 0,
			message:  "nil shares",
		},
		{
			left:     []float64{},
			right:    []float64{},
			expected: 0,
			message:  "empty shares",
		},
		{
			left:     []float64{0, 5},
			right:    []float64{0, 5},
			expected: 0,
			message:  "same shares",
		},
		{
			left:     []float64{0, 10.0},
			right:    []float64{10.0},
			expected: 0,
			message:  "highest same, less shares on one side, zero values",
		},
		{
			left:     []float64{10.0},
			right:    []float64{0, 10.0},
			expected: 0,
			message:  "highest same, less shares on one side, zero values",
		},
		{
			left:     []float64{1, 10},
			right:    []float64{10},
			expected: 1,
			message:  "highest same, less shares on one side, positive values",
		},
		{
			left:     []float64{10},
			right:    []float64{1, 10},
			expected: -1,
			message:  "highest same, less shares on one side, positive values",
		},
		{
			left:     []float64{-10, 10},
			right:    []float64{10},
			expected: -1,
			message:  "highest same, less shares on one side, negative values",
		},
		{
			left:     []float64{10},
			right:    []float64{-10, 10},
			expected: 1,
			message:  "highest same, less shares on one side, negative values",
		},
		{
			left:     []float64{0, 10},
			right:    []float64{5},
			expected: 1,
			message:  "highest is smaller, less shares on one side, values are not important",
		},
		{
			left:     []float64{math.Inf(-1), 0, 0, math.Inf(1)},
			right:    []float64{math.Inf(1)},
			expected: -1,
			message:  "highest is +Inf, less shares on one side, zeros before -Inf value",
		},
		{
			left:     []float64{-100, -10, 0, 1.1, 2.2, 3.3, 5, math.Inf(1)},
			right:    []float64{-99.99, -10, 0, 1.1, 2.2, 3.3, 5, math.Inf(1)},
			expected: -1,
			message:  "longer list of values",
		},
	}

	for _, tc := range tests {
		t.Run(tc.message, func(t *testing.T) {
			comp := compareShares(tc.left, tc.right)
			if comp != tc.expected {
				t.Errorf("incorrect comparison for %s: expected %d got: %d", tc.message, tc.expected, comp)
			}
		})
	}
}

// This tests just the special code in the FairnessRatio.
// This does not check the share calculation see TestGetShares for that.
func TestFairnessRatio(t *testing.T) {
	tests := []struct {
		left            *Resource
		right           *Resource
		total           *Resource
		expectedRatio   float64
		expectedIsInf   bool
		expectedInfSign int
		message         string
	}{
		{
			left:            NewResource(),
			right:           NewResource(),
			total:           NewResource(),
			expectedRatio:   1,
			expectedIsInf:   false,
			expectedInfSign: 0,
			message:         "all empty or nil",
		},
		{
			left:            &Resource{Resources: map[string]Quantity{"first": 1}},
			right:           NewResource(),
			total:           &Resource{Resources: map[string]Quantity{"first": 10}},
			expectedRatio:   math.Inf(1),
			expectedIsInf:   true,
			expectedInfSign: 1,
			message:         "positive left, zero right",
		},
		{
			left:            &Resource{Resources: map[string]Quantity{"first": -1}},
			right:           NewResource(),
			total:           &Resource{Resources: map[string]Quantity{"first": 10}},
			expectedRatio:   math.Inf(-1),
			expectedIsInf:   true,
			expectedInfSign: -1,
			message:         "negative left, zero right",
		},
		{
			left:            &Resource{Resources: map[string]Quantity{"first": math.MaxInt64}},
			right:           &Resource{Resources: map[string]Quantity{"first": 1}},
			total:           &Resource{Resources: map[string]Quantity{"first": math.MaxInt64}},
			expectedRatio:   float64(math.MaxInt64),
			expectedIsInf:   false,
			expectedInfSign: 0,
			message:         "max quantaties on left, 1 on right",
		},
		{
			left:            &Resource{Resources: map[string]Quantity{"first": math.MaxInt64}},
			right:           &Resource{Resources: map[string]Quantity{"first": -1}},
			total:           &Resource{Resources: map[string]Quantity{"first": math.MaxInt64}},
			expectedRatio:   float64(math.MinInt64),
			expectedIsInf:   false,
			expectedInfSign: 0,
			message:         "max quantaties on left, -1 on right",
		},
		{
			left:            &Resource{Resources: map[string]Quantity{"first": 90}},
			right:           &Resource{Resources: map[string]Quantity{"first": 1}},
			total:           &Resource{Resources: map[string]Quantity{"first": 100}},
			expectedRatio:   90,
			expectedIsInf:   false,
			expectedInfSign: 0,
			message:         "left > right",
		},
		{
			left:            &Resource{Resources: map[string]Quantity{"first": -90}},
			right:           &Resource{Resources: map[string]Quantity{"first": 1}},
			total:           &Resource{Resources: map[string]Quantity{"first": 100}},
			expectedRatio:   -90,
			expectedIsInf:   false,
			expectedInfSign: 0,
			message:         "left > right (negative values)",
		},
		{
			left:            &Resource{Resources: map[string]Quantity{"first": 1}},
			right:           &Resource{Resources: map[string]Quantity{"first": 90}},
			total:           &Resource{Resources: map[string]Quantity{"first": 100}},
			expectedRatio:   (1.0 / 100.0) / (90.0 / 100.0),
			expectedIsInf:   false,
			expectedInfSign: 0,
			message:         "right > left",
		},
		{
			left:            &Resource{Resources: map[string]Quantity{"first": -1}},
			right:           &Resource{Resources: map[string]Quantity{"first": 90}},
			total:           &Resource{Resources: map[string]Quantity{"first": 100}},
			expectedRatio:   -(1.0 / 100.0) / (90.0 / 100.0),
			expectedIsInf:   false,
			expectedInfSign: 0,
			message:         "right > left (negative values)",
		},
	}

	for _, tc := range tests {
		t.Run(tc.message, func(t *testing.T) {
			fairRatio := FairnessRatio(tc.left, tc.right, tc.total)

			if tc.expectedIsInf {
				if !math.IsInf(fairRatio, tc.expectedInfSign) {
					t.Errorf("%s: expected Inf with sign %d, got: %f", tc.message, tc.expectedInfSign, fairRatio)
				}
			} else {
				if fairRatio != tc.expectedRatio {
					t.Errorf("%s: expected ratio %f, got: %f", tc.message, tc.expectedRatio, fairRatio)
				}
			}
		})
	}
}

// This tests just to cover code in the CompUsageRatioSeparately
func TestCompUsageRatioSeparately(t *testing.T) {
	tests := []struct {
		leftAllocated   *Resource
		rightAllocated  *Resource
		leftGuaranteed  *Resource
		rightGuaranteed *Resource
		leftFairMax     *Resource
		rightFairMax    *Resource
		expectedRatio   int
		message         string
	}{
		{
			leftAllocated:   NewResource(),
			rightAllocated:  NewResource(),
			leftGuaranteed:  NewResource(),
			rightGuaranteed: NewResource(),
			leftFairMax:     NewResource(),
			rightFairMax:    NewResource(),
			expectedRatio:   0,
			message:         "empty resources",
		},
		{
			leftAllocated:   &Resource{Resources: map[string]Quantity{"ephemeral-storage": 100, "memory": 100, "pods": 100, "vcore": 100}},
			rightAllocated:  &Resource{Resources: map[string]Quantity{"ephemeral-storage": 100, "memory": 100, "pods": 100, "vcore": 100}},
			leftGuaranteed:  &Resource{Resources: map[string]Quantity{"pods": 200}},
			rightGuaranteed: &Resource{Resources: map[string]Quantity{"pods": 800}},
			leftFairMax:     &Resource{Resources: map[string]Quantity{"ephemeral-storage": 1000, "memory": 1000, "pods": 1000, "vcore": 1000}},
			rightFairMax:    &Resource{Resources: map[string]Quantity{"ephemeral-storage": 1000, "memory": 1000, "pods": 1000, "vcore": 1000}},
			expectedRatio:   1,
			message:         "with gaurantees, left has larger share",
		},
		{
			leftAllocated:   &Resource{Resources: map[string]Quantity{"ephemeral-storage": 100, "memory": 100, "pods": 100, "vcore": 100}},
			rightAllocated:  &Resource{Resources: map[string]Quantity{"ephemeral-storage": 100, "memory": 100, "pods": 100, "vcore": 100}},
			leftGuaranteed:  &Resource{Resources: map[string]Quantity{"pods": 800}},
			rightGuaranteed: &Resource{Resources: map[string]Quantity{"pods": 200}},
			leftFairMax:     &Resource{Resources: map[string]Quantity{"ephemeral-storage": 1000, "memory": 1000, "pods": 1000, "vcore": 1000}},
			rightFairMax:    &Resource{Resources: map[string]Quantity{"ephemeral-storage": 1000, "memory": 1000, "pods": 1000, "vcore": 1000}},
			expectedRatio:   -1,
			message:         "with gaurantees, right has larger share",
		},
		{
			leftAllocated:   &Resource{Resources: map[string]Quantity{"ephemeral-storage": 200, "memory": 200, "pods": 200, "vcore": 200}},
			rightAllocated:  &Resource{Resources: map[string]Quantity{"ephemeral-storage": 100, "memory": 100, "pods": 100, "vcore": 100}},
			leftGuaranteed:  NewResource(),
			rightGuaranteed: NewResource(),
			leftFairMax:     &Resource{Resources: map[string]Quantity{"ephemeral-storage": 1000, "memory": 1000, "pods": 1000, "vcore": 1000}},
			rightFairMax:    &Resource{Resources: map[string]Quantity{"ephemeral-storage": 1000, "memory": 1000, "pods": 1000, "vcore": 1000}},
			expectedRatio:   1,
			message:         "no gaurantees, left has larger share",
		},
		{
			leftAllocated:   &Resource{Resources: map[string]Quantity{"ephemeral-storage": 100, "memory": 100, "pods": 100, "vcore": 100}},
			rightAllocated:  &Resource{Resources: map[string]Quantity{"ephemeral-storage": 200, "memory": 200, "pods": 200, "vcore": 200}},
			leftGuaranteed:  NewResource(),
			rightGuaranteed: NewResource(),
			leftFairMax:     &Resource{Resources: map[string]Quantity{"ephemeral-storage": 1000, "memory": 1000, "pods": 1000, "vcore": 1000}},
			rightFairMax:    &Resource{Resources: map[string]Quantity{"ephemeral-storage": 1000, "memory": 1000, "pods": 1000, "vcore": 1000}},
			expectedRatio:   -1,
			message:         "no gaurantees, right has larger share",
		},
		{
			leftAllocated:   &Resource{Resources: map[string]Quantity{"ephemeral-storage": 100, "memory": 100, "pods": 100, "vcore": 100}},
			rightAllocated:  &Resource{Resources: map[string]Quantity{"ephemeral-storage": 100, "memory": 100, "pods": 100, "vcore": 100}},
			leftGuaranteed:  NewResource(),
			rightGuaranteed: NewResource(),
			leftFairMax:     &Resource{Resources: map[string]Quantity{"ephemeral-storage": 1000, "memory": 1000, "pods": 1000, "vcore": 1000}},
			rightFairMax:    &Resource{Resources: map[string]Quantity{"ephemeral-storage": 1000, "memory": 1000, "pods": 1000, "vcore": 1000}},
			expectedRatio:   0,
			message:         "no guarantees, tie",
		},
	}

	for _, tc := range tests {
		t.Run(tc.message, func(t *testing.T) {
			ratio := CompUsageRatioSeparately(tc.leftAllocated, tc.leftGuaranteed, tc.leftFairMax, tc.rightAllocated, tc.rightGuaranteed, tc.rightFairMax)
			if ratio != tc.expectedRatio {
				t.Errorf("%s: expected ratio %d, got: %d", tc.message, tc.expectedRatio, ratio)
			}
		})
	}
}

func TestFitInScoreNil(t *testing.T) {
	// make sure we're nil safe IDE will complain about the non nil check
	defer func() {
		if r := recover(); r != nil {
			t.Fatal("panic on nil resource in fitinscore test")
		}
	}()
	var empty *Resource
	// nil dereference
	if empty.FitInScore(nil) != 0 {
		t.Error("FitInScore on nil receiver failed")
	}
	fit := NewResourceFromMap(map[string]Quantity{"first": 0})
	if empty.FitInScore(fit) != 1 {
		t.Error("FitInScore on nil receiver failed")
	}
}

func TestFitInScore(t *testing.T) {
	testCases := []struct {
		message      string
		receiver     *Resource
		fit          *Resource
		expected     float64
		errorMessage string
	}{
		{
			message:      "Nil input",
			receiver:     NewResource(),
			fit:          nil,
			expected:     0.0,
			errorMessage: "FitInScore with nil input failed",
		},
		{
			message:      "Zero resource",
			receiver:     NewResource(),
			fit:          NewResourceFromMap(map[string]Quantity{"first": 0}),
			expected:     0.0,
			errorMessage: "FitInScore on zero resource failed",
		},
		{
			message:      "Resource with one quantity",
			receiver:     NewResourceFromMap(map[string]Quantity{"first": 10}),
			fit:          NewResourceFromMap(map[string]Quantity{"first": 10}),
			expected:     0.0,
			errorMessage: "FitInScore on resource with one quantity failed",
		},
		{
			message:      "Resource with one quantity of negative value",
			receiver:     NewResourceFromMap(map[string]Quantity{"first": -10}),
			fit:          NewResourceFromMap(map[string]Quantity{"first": 10}),
			expected:     1,
			errorMessage: "FitInScore on resource with one quantity of negative value failed",
		},
		{
			message:      "Resource with multi quantity of negative value",
			receiver:     NewResourceFromMap(map[string]Quantity{"first": -10, "second": -5}),
			fit:          NewResourceFromMap(map[string]Quantity{"first": 10, "second": 10}),
			expected:     2,
			errorMessage: "FitInScore on resource with multi quantity of negative value failed",
		},
		{
			message:      "Fit quantity more than resource",
			receiver:     NewResourceFromMap(map[string]Quantity{"first": 10}),
			fit:          NewResourceFromMap(map[string]Quantity{"first": 20}),
			expected:     0.5,
			errorMessage: "FitInScore on fit quantity more than resource failed",
		},
		{
			message:      "Multi fit quantity more than resource",
			receiver:     NewResourceFromMap(map[string]Quantity{"first": 10, "second": 10}),
			fit:          NewResourceFromMap(map[string]Quantity{"first": 20, "second": 20}),
			expected:     1,
			errorMessage: "FitInScore on multi fit quantity more than resource failed",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.message, func(t *testing.T) {
			result := tc.receiver.FitInScore(tc.fit)
			assert.Equal(t, tc.expected, result, tc.errorMessage)
		})
	}
}

func TestCalculateAbsUsedCapacity(t *testing.T) {
	zeroResource := NewResourceFromMap(map[string]Quantity{"memory": 0, "vcores": 0})
	resourceSet := NewResourceFromMap(map[string]Quantity{"memory": 2048, "vcores": 3})
	usageSet := NewResourceFromMap(map[string]Quantity{"memory": 1024, "vcores": 1})
	partialResource := NewResourceFromMap(map[string]Quantity{"memory": 1024})
	emptyResource := NewResource()

	tests := map[string]struct {
		capacity, used, expected *Resource
	}{
		"nil resource, nil usage": {
			expected: emptyResource,
		},
		"zero resource, nil usage": {
			capacity: zeroResource,
			expected: emptyResource,
		},
		"resource set, nil usage": {
			capacity: resourceSet,
			expected: emptyResource,
		},
		"resource set, zero usage": {
			capacity: resourceSet,
			used:     zeroResource,
			expected: zeroResource,
		},
		"resource set, usage set": {
			capacity: resourceSet,
			used:     usageSet,
			expected: NewResourceFromMap(map[string]Quantity{"memory": 50, "vcores": 33}),
		},
		"partial resource set, usage set": {
			capacity: partialResource,
			used:     usageSet,
			expected: NewResourceFromMap(map[string]Quantity{"memory": 100}),
		},
		"resource set, partial usage set": {
			capacity: resourceSet,
			used:     partialResource,
			expected: NewResourceFromMap(map[string]Quantity{"memory": 50}),
		},
		"positive limit": {
			capacity: NewResourceFromMap(map[string]Quantity{"memory": 1}),
			used:     NewResourceFromMap(map[string]Quantity{"memory": math.MaxInt64}),
			expected: NewResourceFromMap(map[string]Quantity{"memory": math.MaxInt32}),
		},
		"negative overflow": {
			capacity: NewResourceFromMap(map[string]Quantity{"memory": 10}),
			used:     NewResourceFromMap(map[string]Quantity{"memory": math.MinInt64}),
			expected: NewResourceFromMap(map[string]Quantity{"memory": 0}),
		},
		"zero resource, non zero used": {
			capacity: zeroResource,
			used:     usageSet,
			expected: NewResourceFromMap(map[string]Quantity{"memory": 100, "vcores": 100}),
		},
		"min used": {
			capacity: NewResourceFromMap(map[string]Quantity{"memory": math.MinInt64}),
			used:     NewResourceFromMap(map[string]Quantity{"memory": 10}),
			expected: NewResourceFromMap(map[string]Quantity{"memory": 100}),
		},
		"max used": {
			capacity: NewResourceFromMap(map[string]Quantity{"memory": math.MaxInt64}),
			used:     NewResourceFromMap(map[string]Quantity{"memory": math.MaxInt64}),
			expected: NewResourceFromMap(map[string]Quantity{"memory": 100}),
		},
		"multiple resources not in used": {
			capacity: NewResourceFromMap(map[string]Quantity{"memory": 1024, "vcores": 2, "gpu": 1}),
			used:     NewResourceFromMap(map[string]Quantity{"memory": 512}),
			expected: NewResourceFromMap(map[string]Quantity{"memory": 50}),
		},
	}
	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			absCapacity := CalculateAbsUsedCapacity(test.capacity, test.used)
			assert.Assert(t, Equals(test.expected, absCapacity), name)
		})
	}
}

func TestNewResourceFromString(t *testing.T) {
	tests := map[string]struct {
		jsonRes  string
		fail     bool
		expected *Resource
	}{
		"empty string": {
			jsonRes:  "",
			fail:     false,
			expected: nil,
		},
		"empty json": {
			jsonRes:  "{\"resources\":{}}",
			fail:     false,
			expected: NewResource(),
		},
		"not a map": {
			jsonRes:  "{\"resources\":{error}}",
			fail:     true,
			expected: nil,
		},
		"illegal json": {
			jsonRes:  "{\"resources\":{\"missing curly bracket\":{\"value\":5}}",
			fail:     true,
			expected: nil,
		},
		"illegal value": {
			jsonRes:  "{\"resources\":{\"invalid\":{\"value\":\"error\"}}}",
			fail:     true,
			expected: nil,
		},
		"simple": {
			jsonRes:  "{\"resources\":{\"valid\":{\"value\":10}}}",
			fail:     false,
			expected: NewResourceFromMap(map[string]Quantity{"valid": 10}),
		},
		"double": {
			jsonRes:  "{\"resources\":{\"valid\":{\"value\":10}, \"other\":{\"value\":5}}}",
			fail:     false,
			expected: NewResourceFromMap(map[string]Quantity{"valid": 10, "other": 5}),
		},
		"same twice": {
			jsonRes:  "{\"resources\":{\"negative\":{\"value\":10}, \"negative\":{\"value\":-10}}}",
			fail:     false,
			expected: NewResourceFromMap(map[string]Quantity{"negative": -10}),
		},
	}
	for name, test := range tests {
		fromJSON, err := NewResourceFromString(test.jsonRes)
		if test.fail {
			if err == nil || fromJSON != nil {
				t.Errorf("expected error and nil resource for test: %s got results", name)
			}
		} else {
			if test.expected == nil && fromJSON != nil {
				t.Errorf("expected nil resource for test: %s, got: %s", name, fromJSON.String())
			} else if !Equals(fromJSON, test.expected) {
				t.Errorf("returned resource did not match expected resource for test: %s, expected %s, got: %v", name, test.expected, fromJSON)
			}
		}
	}
}

func TestDAOMapNil(t *testing.T) {
	// make sure we're nil safe IDE will complain about the non nil check
	defer func() {
		if r := recover(); r != nil {
			t.Fatal("panic on nil resource in daomap test")
		}
	}()
	var empty *Resource
	assert.DeepEqual(t, empty.DAOMap(), map[string]int64{})
}

func TestDAOMap(t *testing.T) {
	tests := map[string]struct {
		dao map[string]int64
		res *Resource
	}{
		"empty resource": {
			dao: map[string]int64{},
			res: NewResource(),
		},
		"single value": {
			dao: map[string]int64{"first": 1},
			res: NewResourceFromMap(map[string]Quantity{"first": 1}),
		},
		"two values": {
			dao: map[string]int64{"first": 10, "second": -10},
			res: NewResourceFromMap(map[string]Quantity{"first": 10, "second": -10}),
		},
	}
	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			assert.DeepEqual(t, test.res.DAOMap(), test.dao)
		})
	}
}

func TestToString(t *testing.T) {
	defer func() {
		if r := recover(); r != nil {
			t.Fatal("String panic on nil resource")
		}
	}()
	var res *Resource
	// ignore nil check from IDE we really want to do this
	resString := res.String()
	assert.Equal(t, resString, "nil resource", "Unexpected string returned for nil resource")
}

func TestString(t *testing.T) {
	testCases := []struct {
		name           string
		input          Quantity
		expectedResult string
	}{
		{"Negative quantity", -1, "-1"},
		{"Zero quantity", 0, "0"},
		{"Positive quantity", 18, "18"},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			assert.Equal(t, tc.expectedResult, tc.input.string())
		})
	}
}

func TestHasNegativeValue(t *testing.T) {
	testCases := []struct {
		name           string
		input          *Resource
		expectedResult bool
	}{
		{"Nil resource", nil, false},
		{"Empty resource", NewResource(), false},
		{"Only positive values", NewResourceFromMap(map[string]Quantity{common.Memory: 100}), false},
		{"Negative value", NewResourceFromMap(map[string]Quantity{common.Memory: -100}), true},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			assert.Equal(t, tc.expectedResult, tc.input.HasNegativeValue())
		})
	}
}

func TestIsEmpty(t *testing.T) {
	testCases := []struct {
		name           string
		input          *Resource
		expectedResult bool
	}{
		{"Nil resource", nil, true},
		{"Empty resource", NewResource(), true},
		{"Positive value", NewResourceFromMap(map[string]Quantity{common.Memory: 100}), false},
		{"Negative value", NewResourceFromMap(map[string]Quantity{common.Memory: -100}), false},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			assert.Equal(t, tc.expectedResult, tc.input.IsEmpty())
		})
	}
}

func TestResource_DominantResource(t *testing.T) {
	tests := []struct {
		name     string
		used     *Resource
		capacity *Resource
		wantName string
	}{
		{"nil receiver", nil, Zero, ""},
		{"nil cap", Zero, nil, ""},
		{"zero cap", NewResourceFromMap(map[string]Quantity{"A": 10}), Zero, ""},
		{"over cap", NewResourceFromMap(map[string]Quantity{"A": 20}), NewResourceFromMap(map[string]Quantity{"A": 10}), "A"},
		{"zero usage exist", NewResourceFromMap(map[string]Quantity{"A": 0}), NewResourceFromMap(map[string]Quantity{"A": 10}), "A"},
		{"usage not in cap", NewResourceFromMap(map[string]Quantity{"B": 10}), NewResourceFromMap(map[string]Quantity{"A": 10}), ""},
		{"multiple usages", NewResourceFromMap(map[string]Quantity{"B": 10, "A": 10}), NewResourceFromMap(map[string]Quantity{"A": 10, "B": 20}), "A"},
		{"0 usage with 0 cap", NewResourceFromMap(map[string]Quantity{"A": 0}), NewResourceFromMap(map[string]Quantity{"A": 0}), "A"},
		{"usage with 0 cap", NewResourceFromMap(map[string]Quantity{"A": 10, "B": 5}), NewResourceFromMap(map[string]Quantity{"A": 0, "B": 10}), "A"},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := tt.used.DominantResourceType(tt.capacity); got != tt.wantName {
				t.Errorf("DominantResourceType() = %v, want %v", got, tt.wantName)
			}
		})
	}
}

func TestResource_PruneNil(t *testing.T) {
	// make sure we're nil safe IDE will complain about the receiver being nil
	defer func() {
		if r := recover(); r != nil {
			t.Fatal("panic on nil resource in prune test")
		}
	}()
	var empty *Resource
	empty.Prune()
}

func TestResource_Prune(t *testing.T) {
	var tests = []struct {
		caseName string
		input    map[string]Quantity
		output   map[string]Quantity
	}{
		{"no types", map[string]Quantity{}, map[string]Quantity{}},
		{"all types with value", map[string]Quantity{"first": 1, "second": -2, "third": 3}, map[string]Quantity{"first": 1, "second": -2, "third": 3}},
		{"zero type", map[string]Quantity{"first": 1, "zero": 0, "third": 3}, map[string]Quantity{"first": 1, "third": 3}},
		{"no types with value", map[string]Quantity{"first": 0, "second": 0}, map[string]Quantity{}},
	}
	for _, tt := range tests {
		t.Run(tt.caseName, func(t *testing.T) {
			original := NewResourceFromMap(tt.input)
			original.Prune()
			assert.Equal(t, len(tt.output), len(original.Resources), "unexpected resource types returned")
			assert.Assert(t, maps.Equal(original.Resources, tt.output), "resource type maps are not equal")
		})
	}
}
